Skip to content

Conversation

@SzucsAti
Copy link
Contributor

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

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jun 29, 2021

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 /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions 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.

@openshift-ci openshift-ci bot added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Jun 29, 2021
@openshift-ci openshift-ci bot requested review from alebedev87 and sgreene570 June 29, 2021 09:14
@sgreene570
Copy link
Contributor

/ok-to-test

@openshift-ci openshift-ci bot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Jun 29, 2021
@SzucsAti
Copy link
Contributor Author

/retest

1 similar comment
@SzucsAti
Copy link
Contributor Author

/retest

@SzucsAti
Copy link
Contributor Author

SzucsAti commented Jul 5, 2021

Removed unnecessary changes from go.mod.

@SzucsAti
Copy link
Contributor Author

SzucsAti commented Jul 5, 2021

/retest

1 similar comment
@SzucsAti
Copy link
Contributor Author

SzucsAti commented Jul 5, 2021

/retest

@jeffnowicki
Copy link

/cc @Miciah

@openshift-ci openshift-ci bot requested a review from Miciah July 6, 2021 16:06
@fabianofranz
Copy link
Member

/assign @Miciah

kind: IBMCloudProviderSpec
secretRef:
name: cloud-credentials
namespace: openshift-ingress-operator
Copy link
Contributor

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?

}
if result != nil && result.Result != nil {
for _, resultData := range result.Result {
if resultData.ID != nil {
Copy link
Contributor

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)

Copy link
Contributor

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

Copy link
Contributor

@Miciah Miciah left a 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.


dnsService, err := dnsrecordsv1.NewDnsRecordsV1(options)
if err != nil {
return nil, fmt.Errorf("failed to create a new DNS Service instance: %v", err)
Copy link
Contributor

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:

Suggested change
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.

if err := validateInputDNSData(record, zone); err != nil {
return fmt.Errorf("delete: invalid dns input data: %v", err)
}
dnsService := p.dnsServices[zone.ID]
Copy link
Contributor

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?

Suggested change
dnsService := p.dnsServices[zone.ID]
dnsService, ok := p.dnsServices[zone.ID]
if !ok {
return fmt.Errorf("delete: unknown zone: %v", zone.ID)
}

if err := validateInputDNSData(record, zone); err != nil {
return fmt.Errorf("createOrUpdateDNSRecord: invalid dns input data: %v", err)
}
dnsService := p.dnsServices[zone.ID]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dnsService := p.dnsServices[zone.ID]
dnsService, ok := p.dnsServices[zone.ID]
if !ok {
return fmt.Errorf("createOrUpdateDNSRecord: unknown zone: %v", zone.ID)
}

listOpt.SetContent(target)
result, _, err := dnsService.ListAllDnsRecords(listOpt)
if err != nil {
return fmt.Errorf("createOrUpdateDNSRecord: failed to list the dns record: %v", err)
Copy link
Contributor

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?

Comment on lines 170 to 185
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
Copy link
Contributor

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)

Comment on lines 15 to 27

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
}
Copy link
Contributor

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.

Comment on lines 142 to 166
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)
}
}
Copy link
Contributor

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)

Comment on lines 141 to 143
if tc.expectedErr {
if err == nil {
t.Error("Expected error, but err is nil")
}
}
Copy link
Contributor

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?

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)
Copy link
Contributor

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:

Suggested change
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)

Comment on lines 259 to 263
if tc.expectedErr {
if err == nil {
t.Error("Expected error, but err is nil")
}
}
Copy link
Contributor

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 *.

Copy link
Contributor

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,
		},

Copy link
Contributor

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.

@SzucsAti
Copy link
Contributor Author

SzucsAti commented Jul 8, 2021

Thank you for the reviews! Finished with resolving your comments and implemented the requested changes.

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)
Copy link
Contributor

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

if err != nil && delResponse.StatusCode != http.StatusNotFound {
return fmt.Errorf("delete: failed to delete the dns record: %w", err)
}
if delResponse.StatusCode != http.StatusNotFound {
Copy link
Contributor

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 {
Copy link
Contributor

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:

Suggested change
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:

Suggested change
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 {
Copy link
Contributor

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).

Copy link
Contributor Author

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

infraStatus cannot be nil.

Suggested change
if infraStatus != nil && infraStatus.ControlPlaneTopology == configv1.ExternalTopologyMode {
if infraStatus.ControlPlaneTopology == configv1.ExternalTopologyMode {

@Miciah
Copy link
Contributor

Miciah commented Jul 14, 2021

Looks great! Thanks for your patience!
/approve/
/lgtm

@Miciah
Copy link
Contributor

Miciah commented Jul 14, 2021

/approve

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Jul 14, 2021
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jul 14, 2021

[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

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 14, 2021
@openshift-merge-robot openshift-merge-robot merged commit fd7937d into openshift:master Jul 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants