-
Notifications
You must be signed in to change notification settings - Fork 219
Add IBMCloud DNS support #630
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
Conversation
|
Hi @SzucsAti. Thanks for your PR. I'm waiting for a openshift member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
|
/ok-to-test |
|
/retest |
1 similar comment
|
/retest |
|
Removed unnecessary changes from go.mod. |
|
/retest |
1 similar comment
|
/retest |
|
/cc @Miciah |
|
/assign @Miciah |
| kind: IBMCloudProviderSpec | ||
| secretRef: | ||
| name: cloud-credentials | ||
| namespace: openshift-ingress-operator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add a newline to end of this file?
pkg/dns/ibm/dns.go
Outdated
| } | ||
| if result != nil && result.Result != nil { | ||
| for _, resultData := range result.Result { | ||
| if resultData.ID != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be cleaner to check if resultData.ID == nil here and return the error on line 121 if it is (rather than indent the rest of the code path)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This suggestion might apply to other place as well
Miciah
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please run go mod tidy and squash.
pkg/dns/ibm/dns.go
Outdated
|
|
||
| dnsService, err := dnsrecordsv1.NewDnsRecordsV1(options) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to create a new DNS Service instance: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've been moving towards using %w where possible with fmt.Errorf and the like:
| return nil, fmt.Errorf("failed to create a new DNS Service instance: %v", err) | |
| return nil, fmt.Errorf("failed to create a new DNS Service instance: %w", err) |
That said, we already have a lot of old code that we need to update to use %w, so we can get this too in a follow-up if you prefer.
pkg/dns/ibm/dns.go
Outdated
| if err := validateInputDNSData(record, zone); err != nil { | ||
| return fmt.Errorf("delete: invalid dns input data: %v", err) | ||
| } | ||
| dnsService := p.dnsServices[zone.ID] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot be sure at this point that we have a DNS service instance for the given zone, can we?
| dnsService := p.dnsServices[zone.ID] | |
| dnsService, ok := p.dnsServices[zone.ID] | |
| if !ok { | |
| return fmt.Errorf("delete: unknown zone: %v", zone.ID) | |
| } |
pkg/dns/ibm/dns.go
Outdated
| if err := validateInputDNSData(record, zone); err != nil { | ||
| return fmt.Errorf("createOrUpdateDNSRecord: invalid dns input data: %v", err) | ||
| } | ||
| dnsService := p.dnsServices[zone.ID] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| dnsService := p.dnsServices[zone.ID] | |
| dnsService, ok := p.dnsServices[zone.ID] | |
| if !ok { | |
| return fmt.Errorf("createOrUpdateDNSRecord: unknown zone: %v", zone.ID) | |
| } |
pkg/dns/ibm/dns.go
Outdated
| listOpt.SetContent(target) | ||
| result, _, err := dnsService.ListAllDnsRecords(listOpt) | ||
| if err != nil { | ||
| return fmt.Errorf("createOrUpdateDNSRecord: failed to list the dns record: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you check for http.StatusNotFound here as you do in the delete logic? Especially in the case of a create, don't you expect to get a "not found" response to this list operation?
pkg/dns/ibm/dns.go
Outdated
| if record == nil { | ||
| return fmt.Errorf("validateInputDNSData: dns record is nil") | ||
| } | ||
| if record.Spec.DNSName == "" { | ||
| return fmt.Errorf("validateInputDNSData: dns record name is empty") | ||
| } | ||
| if record.Spec.RecordType == "" { | ||
| return fmt.Errorf("validateInputDNSData: dns record type is empty") | ||
| } | ||
| if len(record.Spec.Targets) == 0 { | ||
| return fmt.Errorf("validateInputDNSData: dns record content is empty") | ||
| } | ||
| if zone.ID == "" { | ||
| return fmt.Errorf("validateInputDNSData: dns zone id is empty") | ||
| } | ||
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be worth aggregating errors like you do in validateDNSServices?
var errs []error
if record == nil {
errs = append(errs, fmt.Errorf("validateInputDNSData: dns record is nil"))
} else {
if len(record.Spec.DNSName) == 0 {
errs = append(errs, fmt.Errorf("validateInputDNSData: dns record name is empty"))
}
if len(record.Spec.RecordType) == 0 {
errs = append(errs, fmt.Errorf("validateInputDNSData: dns record type is empty"))
}
if len(record.Spec.Targets) == 0 {
errs = append(errs, fmt.Errorf("validateInputDNSData: dns record content is empty"))
}
}
if len(zone.ID) == 0 {
errs = append(errs, fmt.Errorf("validateInputDNSData: dns zone id is empty"))
}
return kerrors.NewAggregate(errs)
pkg/dns/ibm/dns_test.go
Outdated
|
|
||
| provider := &Provider{} | ||
|
|
||
| dnsService, err := dnsclient.NewFake() | ||
|
|
||
| provider.dnsServices = make(map[string]dnsclient.DnsClient) | ||
|
|
||
| zone := configv1.DNSZone{ | ||
| ID: "zoneID", | ||
| } | ||
|
|
||
| provider.dnsServices[zone.ID] = dnsService | ||
|
|
||
| if err != nil { | ||
| t.Error("failed to create fakeClient") | ||
| return | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use t.Fatal instead of t.Error plus return, and the initialization can be re-ordered and more logically grouped:
zone := configv1.DNSZone{
ID: "zoneID",
}
dnsService, err := dnsclient.NewFake()
if err != nil {
t.Fatal("failed to create fakeClient")
}
provider := &Provider{}
provider.dnsServices = map[string]dnsclient.DnsClient{
zone.ID: dnsService,
}Likewise in TestCreateOrUpdate.
pkg/dns/ibm/dns.go
Outdated
| if len(result.Result) == 0 { | ||
| createOpt := dnsService.NewCreateDnsRecordOptions() | ||
| createOpt.SetName(record.Spec.DNSName) | ||
| createOpt.SetType(string(record.Spec.RecordType)) | ||
| createOpt.SetContent(target) | ||
| _, _, err := dnsService.CreateDnsRecord(createOpt) | ||
| if err != nil { | ||
| return fmt.Errorf("createOrUpdateDNSRecord: failed to create the dns record: %v", err) | ||
| } | ||
| } else { | ||
| updateOpt := dnsService.NewUpdateDnsRecordOptions(*result.Result[0].ID) | ||
| updateOpt.SetName(record.Spec.DNSName) | ||
| updateOpt.SetType(string(record.Spec.RecordType)) | ||
| updateOpt.SetContent(record.Spec.Targets[0]) | ||
| _, _, err := dnsService.UpdateDnsRecord(updateOpt) | ||
| if err != nil { | ||
| return fmt.Errorf("createOrUpdateDNSRecord: failed to update the dns record: %v", err) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not set TTL?
createOpt.SetContent(target)
createOpt.SetTTL(record.Spec.RecordTTL)
// ...
} else {
// ...
updateOpt.SetContent(target)
updateOpt.SetTTL(record.Spec.RecordTTL)
pkg/dns/ibm/dns_test.go
Outdated
| if tc.expectedErr { | ||
| if err == nil { | ||
| t.Error("Expected error, but err is nil") | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not also check !tc.expectedErr && err != nil?
pkg/dns/ibm/dns_test.go
Outdated
| recordedCall, _ := dnsService.RecordedCall(record.Spec.DNSName) | ||
|
|
||
| if recordedCall != tc.recordedCall { | ||
| t.Errorf("expected the dns client %s func to be called, but found %s instead", tc.recordedCall, recordedCall) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using %q may make this more readable, especially if one or both values are empty:
| t.Errorf("expected the dns client %s func to be called, but found %s instead", tc.recordedCall, recordedCall) | |
| t.Errorf("expected the dns client %q func to be called, but found %q instead", tc.recordedCall, recordedCall) |
pkg/dns/ibm/dns_test.go
Outdated
| if tc.expectedErr { | ||
| if err == nil { | ||
| t.Error("Expected error, but err is nil") | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check !tc.expectedErr && err != nil:
if !tc.expectedErr && err != nil {
t.Errorf("Expected nil err, got %v", err)
}If you do that, I think you'll find that my other comment about checking for "not found" errors in the create/update logic has merit. * grin *.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, on second look, maybe the "not found" errors aren't relevant. This check causes the following failure:
--- FAIL: TestCreateOrUpdate (0.00s)
--- FAIL: TestCreateOrUpdate/listFail (0.00s)
dns_test.go:261: Expected nil err, got createOrUpdateDNSRecord: failed to update the dns record: updateDnsRecord: inputs don't match
That can be fixed by adding updateDnsRecordInputOutput to the test case:
{
desc: "listFail",
DNSName: "testUpdate",
recordedCall: "PUT",
listAllDnsRecordsInputOutput: dnsclient.ListAllDnsRecordsInputOutput{
OutputError: nil,
OutputStatusCode: http.StatusNotFound,
},
updateDnsRecordInputOutput: dnsclient.UpdateDnsRecordInputOutput{
InputId: "testUpdate",
OutputError: nil,
OutputStatusCode: http.StatusOK,
},
expectedErr: false,
},There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, I think the test case should actually look like this:
{
desc: "listFail",
DNSName: "testUpdate",
recordedCall: "PUT",
listAllDnsRecordsInputOutput: dnsclient.ListAllDnsRecordsInputOutput{
OutputError: errors.New("Error in ListAllDnsRecords"),
OutputStatusCode: http.StatusNotFound,
},
updateDnsRecordInputOutput: dnsclient.UpdateDnsRecordInputOutput{
InputId: "testUpdate",
OutputError: nil,
OutputStatusCode: http.StatusOK,
},
expectedErr: false,
},Then I get the following failure:
--- FAIL: TestCreateOrUpdate (0.00s)
--- FAIL: TestCreateOrUpdate/listFail (0.00s)
dns_test.go:266: Expected nil err, got createOrUpdateDNSRecord: failed to list the dns record: Error in ListAllDnsRecords
But if I fix createOrUpdateDNSRecord as follows:
result, response, err := dnsService.ListAllDnsRecords(listOpt)
if err != nil {
if response.StatusCode != http.StatusNotFound {
return fmt.Errorf("createOrUpdateDNSRecord: failed to list the dns record: %w", err)
}
continue
}Then the test passes again.
|
Thank you for the reviews! Finished with resolving your comments and implemented the requested changes. |
pkg/dns/ibm/dns.go
Outdated
| if err != nil && delResponse.StatusCode != http.StatusNotFound { | ||
| return fmt.Errorf("delete: failed to delete the dns record: %w", err) | ||
| } | ||
| log.Info("deleted DNS record", "record", record.Spec, "zone", zone) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will log "deleted DNS record" even if the record didn't exist. Maybe that's all right? ¯\_(ツ)_/¯
| } | ||
| dnsProvider = provider | ||
| case configv1.IBMCloudPlatformType: | ||
| if infraStatus.ControlPlaneTopology != configv1.ExternalTopologyMode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked asked about a similar if infraStatus.ControlPlaneTopology != configv1.ExternalTopologyMode check in createDNSProviderIfNeeded, but I forgot to comment on this. Can this check be done for all providers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it can. Changed it accordingly.
pkg/dns/ibm/dns.go
Outdated
| if err != nil && delResponse.StatusCode != http.StatusNotFound { | ||
| return fmt.Errorf("delete: failed to delete the dns record: %w", err) | ||
| } | ||
| if delResponse.StatusCode != http.StatusNotFound { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to check to make sure delResponse isn't nil? Come to think of it, we might need to check for nil values where we use the result or response value from dnsService.ListAllDnsRecords as well.
| name := types.NamespacedName{Namespace: r.config.Namespace, Name: cloudCredentialsSecretName} | ||
| if err := r.cache.Get(context.TODO(), name, creds); err != nil { | ||
| return fmt.Errorf("failed to get cloud credentials from secret %s: %v", name, err) | ||
| if (r.infraConfig != nil && infraConfig.Status.ControlPlaneTopology != configv1.ExternalTopologyMode) || r.infraConfig == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition can be simplified:
| if (r.infraConfig != nil && infraConfig.Status.ControlPlaneTopology != configv1.ExternalTopologyMode) || r.infraConfig == nil { | |
| if r.infraConfig == nil || infraConfig.Status.ControlPlaneTopology != configv1.ExternalTopologyMode { |
But really, why check r.infraConfig at all in this switch statement? If the condition r.infraConfig == nil is true, then that means that the if r.infraConfig == nil || ... statement below will set needUpdate = true, and if infraConfig.Status.ControlPlaneTopology == configv1.ExternalTopologyMode is true, then when we call createDNSProvider, it will return without using creds. So I think what you had in the previous version of this PR is sufficient:
| if (r.infraConfig != nil && infraConfig.Status.ControlPlaneTopology != configv1.ExternalTopologyMode) || r.infraConfig == nil { | |
| if infraConfig.Status.ControlPlaneTopology != configv1.ExternalTopologyMode { |
| } | ||
| } | ||
| case configv1.IBMCloudPlatformType: | ||
| if r.infraConfig != nil && infraConfig.Status.ControlPlaneTopology != configv1.ExternalTopologyMode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In https://github.com/openshift/cluster-ingress-operator/compare/634e4b2c2a6f74899a3e310bc0c5c28d449d611d..7000e8a956b886d99353c456ddbb4a2744389b2f, you deleted this separate case branch and added configv1.IBMCloudPlatformType to the case above, and then in https://github.com/openshift/cluster-ingress-operator/compare/75f62202398fee26e230891ec54bc2878f67ba88..ca476de7671bcaadaef41712615bc9db0361d29f, you added this case back with this condition. I don't see why we need the two separate case branches. Is it necessary to skip initialization of the DNS provider on IBM Cloud when r.infraConfig == nil? We should avoid special cases where possible (and it would be nice to avoid the code duplication as well if the special casing isn't required).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the trouble. Internally we had a misunderstanding. Changes reverted.
| return &dns.FakeProvider{}, nil | ||
| } | ||
|
|
||
| if infraStatus != nil && infraStatus.ControlPlaneTopology == configv1.ExternalTopologyMode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
infraStatus cannot be nil.
| if infraStatus != nil && infraStatus.ControlPlaneTopology == configv1.ExternalTopologyMode { | |
| if infraStatus.ControlPlaneTopology == configv1.ExternalTopologyMode { |
|
Looks great! Thanks for your patience! |
|
/approve |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: Miciah, SzucsAti The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
With this PR, ingress-operators running on IBMCloud IPI/UPI clusters will be able to use CIS as their DNS provider.
This PR is required to implement IBMCloud IPI/UPI.
Enhancement doc: openshift/enhancements#773
Relevant Installer PR: openshift/installer#4923