-
Notifications
You must be signed in to change notification settings - Fork 207
OCPBUGS-31521: Don't publish duplicate DNS records #1229
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
base: master
Are you sure you want to change the base?
Conversation
Skipping CI for Draft Pull Request. |
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
for _, existingRecord := range records.Items { | ||
// we only care if the domain name is published by a different record, so ignore the matching record if it | ||
// already exists. | ||
// TODO: There's got to be a better way to match the same object |
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.
Compare by UID
instead of name.
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 is still a relevant comment, but I moved the function, which made github think it's outdated. I'll make this change in my next update
@@ -352,6 +361,24 @@ func (r *reconciler) publishRecordToZones(zones []configv1.DNSZone, record *iov1 | |||
} | |||
} else if isRecordPublished { | |||
condition, err = r.replacePublishedRecord(zones[i], record) | |||
} else if isDomainPublished, err = domainIsAlreadyPublishedInZone(context.Background(), r.cache, record, &zones[i]); err != 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.
You don't need to declare isDomainPublished
outside the else if
clause. Better to keep isDomainPublished
and err
scoped to the else if
clauses.
} else if isDomainPublished, err = domainIsAlreadyPublishedInZone(context.Background(), r.cache, record, &zones[i]); err != nil { | |
} else if isDomainPublished, err := domainIsAlreadyPublishedInZone(context.Background(), r.cache, record, &zones[i]); err != nil { |
Edit: Discussed on a call. Line 388 uses the err
value. This logic is a bit subtle and could use some refactoring.
Reason: "DomainAlreadyInUse", | ||
Status: string(operatorv1.ConditionUnknown), | ||
Type: iov1.DNSRecordPublishedConditionType, | ||
LastTransitionTime: metav1.Now(), |
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 believe mergeConditions
will set LastTransitionTime
, so you don't need to set it here (lines 371 and 380).
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.
523461a updated Scratch that, mergeConditions
to set LastTransitionTime
. That's a fairly recent change, so older code (e.g. line 360) might be setting LastTransitionTime
because it used to be necessary but no longer is.mergeConditions
was setting LastTransitionTime
even before that commit.
Before attempting to publish a domain to a zone, check if that domain is already being published to the same zone.
9fece4b
to
d38f7bf
Compare
func (r *reconciler) MapOnRecordDelete(ctx context.Context, o client.Object) []reconcile.Request { | ||
deletedRecord, ok := o.(*iov1.DNSRecord) | ||
if !ok { | ||
log.Info("failed to read DNS record") |
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.
log.Info("failed to read DNS record") | |
log.Infof("Got unexpected object; expected type DNSRecord, got type %T", o) |
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.
Or something more like this:
log.Info("failed to read DNS record") | |
log.Error(nil, "Got unexpected type of object", "expected", "DNSRecord", "actual", fmt.Sprintf("%T", o)) |
// When a DNS record is deleted, there may be a conflicting record that should be published. Watch exclusively for | ||
// deletes, and queue a reconcile request for the appropriate conflicting record, if applicable. | ||
if err := c.Watch(source.Kind[client.Object](operatorCache, &iov1.DNSRecord{}, handler.EnqueueRequestsFromMapFunc(reconciler.MapOnRecordDelete), predicate.Funcs{ |
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.
Make the comment a little more explicit that, yes, we have two watches on the same resource (dnsrecords), and the reason is so that we can have a predicate and mapfunc to do something special for deletes.
// When a DNS record is deleted, there may be a conflicting record that should be published. Watch exclusively for | ||
// deletes, and queue a reconcile request for the appropriate conflicting record, if applicable. | ||
if err := c.Watch(source.Kind[client.Object](operatorCache, &iov1.DNSRecord{}, handler.EnqueueRequestsFromMapFunc(reconciler.MapOnRecordDelete), predicate.Funcs{ | ||
CreateFunc: func(e event.CreateEvent) bool { return false }, | ||
DeleteFunc: func(e event.DeleteEvent) bool { return true }, |
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.
Add a comment with your findings that the delete event happens when the object is actually deleted, not when it is merely marked for deletion (that is, when deletionTimestamp
is set).
@@ -580,6 +646,37 @@ func (r *reconciler) ToDNSRecords(ctx context.Context, o client.Object) []reconc | |||
return requests | |||
} | |||
|
|||
func (r *reconciler) MapOnRecordDelete(ctx context.Context, o client.Object) []reconcile.Request { |
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.
func (r *reconciler) MapOnRecordDelete(ctx context.Context, o client.Object) []reconcile.Request { | |
func (r *reconciler) mapOnRecordDelete(ctx context.Context, o client.Object) []reconcile.Request { |
@@ -580,6 +646,37 @@ func (r *reconciler) ToDNSRecords(ctx context.Context, o client.Object) []reconc | |||
return requests | |||
} | |||
|
|||
func (r *reconciler) MapOnRecordDelete(ctx context.Context, o client.Object) []reconcile.Request { |
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.
Needs a godoc comment
@@ -840,3 +847,26 @@ func Test_customCABundle(t *testing.T) { | |||
}) | |||
} | |||
} | |||
|
|||
func buildFakeCache(t *testing.T) *testutil.FakeCache { |
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.
Also could use a godoc comment
// correctly merges status updates. | ||
func TestPublishRecordToZonesMergesStatus(t *testing.T) { | ||
func Test_publishRecordToZonesMergesStatus(t *testing.T) { |
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.
TestPublishRecordToZonesMergesStatus
is an appropriate name for the test as there is no publishRecordToZonesMergesStatus
function.
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.
Probably I am also missing something here, but is there a new test case that will check if the condition is set?
@rfredette: This pull request references Jira Issue OCPBUGS-31521, which is invalid:
Comment The bug has been updated to refer to the pull request using the external bug tracker. In response to this:
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 openshift-eng/jira-lifecycle-plugin repository. |
@rfredette: The following tests failed, say
Full PR test history. Your PR dashboard. 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-sigs/prow repository. I understand the commands that are listed here. |
} | ||
oldestExistingRecord := iov1.DNSRecord{} | ||
for _, existingRecord := range otherRecords.Items { | ||
if oldestExistingRecord.CreationTimestamp.IsZero() || existingRecord.CreationTimestamp.Before(&oldestExistingRecord.CreationTimestamp) { |
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.
as a suggestion, first check if existingRecord is marked for deletion, so you don't consider it for reconciliation if it is also on the queue for removal:
if !existingRecord.DeletionTimestamp.IsZero() {
continue
}
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.
That seems reasonable. On the one hand, as noted in #1229 (comment), the reconciliation triggers when the DNSRecord CR is actually deleted, not merely marked for deletion. On the other hand, we could be in a situation where multiple dnsrecords are being deleted at the same time, in which case your suggestion might avoid some unnecessary reconciliations.
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.
yeap, I should have been more explicit but my idea here is to ignore any other DNS record that would be considered older but also being on the deletion queue.
for _, existingRecord := range records.Items { | ||
// we only care if the domain name is published by a different record, so ignore the matching record if it | ||
// already exists. | ||
// TODO: There's got to be a better way to match the same object |
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 match the UID here. I would say that during admission webhooks matching UID is a bad idea, as the UID will be added once persisted by apiserver, but in case of a reconciliation process it means that the UID was already generated and cannot be changed anymore.
iov1.AddToScheme(scheme) | ||
fakeClient := fake.NewClientBuilder(). | ||
WithScheme(scheme). | ||
WithIndex(&iov1.DNSRecord{}, dnsRecordIndexFieldName, func(o client.Object) []string { |
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.
nit (no need to fix now!): do we care about making this indexer function some sort of utils/specific function that can be used both on the operatorCache.IndexField
and on fakeCache to keep consistency?
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 you mean a util function that the controller logic and test logic would share? That does make sense, though I do caution against re-using controller logic in tests if doing so could mask a defect in the controller logic.
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, the idea was to make some util/shared function, but also I see your concerns here, so makes sense also to not share and in case something changes on the main reconciliation logic, the test that has a different cache logic will catch the regression.
/assign @rikatz |
Before attempting to publish a domain to a zone, check if that domain is already being published to the same zone.