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
bulk REST requests #96
Conversation
d970892
to
79ecccf
Compare
I added e2e tests for RRset deletion via bulk requests (setting |
A few details about input validation for bulk requests: There are three stages during which exceptions can occur:
Errors are collected in each stage and presented as a list of errors, with each list item referring to one part of the bulk request, in the same order. Parts that did not cause errors have The treatment of stages 1 and 2 may mean that one bulk part appears without an error in stage 1 ( While it would be possible to merge stages 1 and 2, it would rip apart the usual structure of DRF serializers, and I'm also not sure if it is worth the effort. It probably suffices to mention this behavior in the docs. Errors in stages 1 and 2 cause status code |
Outstanding issues:
Not aware of anything else. |
501b4a1
to
2b18fff
Compare
All done except docs, which I prefer to write once the bulk API has been reviewed. @nils-wisiol Can you look at this PR when you find some time? Thanks! |
c731587
to
4f3f8af
Compare
api/desecapi/settings.py
Outdated
@@ -181,7 +181,7 @@ | |||
DEBUG = True | |||
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend' | |||
ABUSE_BY_REMOTE_IP_LIMIT = 100 | |||
ABUSE_BY_REMOTE_IP_PERIOD_HRS = 4800 | |||
ABUSE_BY_REMOTE_IP_PERIOD_HRS = 2 |
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.
To disable the limit, this should be set to zero.
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, but do we want to disable the limit? We may want to have the functionality available.
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'd say disable it for now, there are no tests that require the limit. It's better to have it definitely disabled than to have it go off at 100 different email addresses, causing random e2e test failures (as the order of the requests is not deterministic).
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.
Ok. I'll also add a pending test for user locking.
api/desecapi/settings.py
Outdated
ABUSE_BY_EMAIL_HOSTNAME_LIMIT = 100 | ||
ABUSE_BY_EMAIL_HOSTNAME_PERIOD_HRS = 2400 | ||
ABUSE_BY_EMAIL_HOSTNAME_PERIOD_HRS = 1 |
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.
To disable the limit, this should be set to zero.
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, but do we want to disable the limit? We may want to have the functionality available.
api/desecapi/models.py
Outdated
@@ -290,6 +315,11 @@ def write_rrsets(self, rrsets): | |||
if rrsets_to_write and not self.owner.locked: | |||
pdns.set_rrsets(self, rrsets_to_write) | |||
|
|||
# Return RRsets written | |||
return [rrsets_return_lookup[rrset] |
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 appreciate the extension to write_rrsets
! I still believe though that this method is too long and complicated for a throughout review -- I advise a refactoring in the near future, splitting functionality in several subroutines. For now, I can approve this based on a general understanding of what's going on and based on the passing tests. (#97)
test/e2e/spec/api_spec.js
Outdated
|
||
it("can register a domain name", function () { | ||
var domain = 'e2etest-' + require("uuid").v4() + '.dedyn.io'; | ||
return expect(chakram.post('/domains/', {'name': domain})).to.have.status(201); | ||
}); | ||
|
||
it("has correct JSON schema"); |
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.
unnecessary/missing function() {
argument?
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.
No. That's a "pending test" (neither failing nor passing); shows up with "-" instead of "x" or checkmark in the results. It's meant to say that this test has no implementation yet.
test/e2e/spec/api_spec.js
Outdated
it("with correct status and schema", function () { | ||
var response = chakram.get('/domains/' + domain + '/rrsets/'); | ||
expect(response).to.have.status(200); | ||
expect(response).to.have.schema(rrsetsSchema); |
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 test will always result in an empty rrsets reply. Although correct, is that the point?
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.
No, the NS
RRset should be there. I'll add a check for it to the test.
Before we merge this, I'd like to add further e2e tests. To that end I updated the e2e dns test interface to use a different dns library, so that dns responses include more details (most importantly, the TTL). Also, it's quite easy to add further RR set types. Example usage of the new code:
More RR set types can easily be added. Note that to my best knowledge, I propose you cherry-pick commits d6e8371 and b7ce50c into your PR and adopt your e2e tests before we proceed. Note that the latter breaks existing test cases that rely on |
Thanks for doing all that work! 👍
I said that
Sounds good. |
eb8293f
to
c9a4f5b
Compare
I cherry-picked as you proposed and refactored all tests to use the new DNS tool. I also added implementations for all tests that were pending (i.e. they had a name only).
I managed to fix things up so that this commit can go away (already dropped it in this branch). I am not sure how I messed up previously. So, I think, we're pretty much done! I have left the new changes as individual commits so that you can easily review them, but before merging, we may want to squash some of them. Here's a list:
I think it will be reasonable to squash 9 -- 13, 14--15, 17--19. Also, as Let me know what you think, both about the status of the PR as well as the squashing! |
yeah, please squash as proposed. Otherwise I'm afraid I'll get caught up in commenting on outdated changes :-) |
c9a4f5b
to
abd8509
Compare
done! |
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 think commits 95ca54d and 48cbfdc should be together in one commit.
The e2e tests 5fa6878 have lots of duplicate code. I know that's hardly avoidable, but let's try to reduce it just a little: maybe some of the it("propagates to the API", function () {
can be refactored in a similar manner like you did with itShowsUpInPdnsAs
?
Also, I'm a fan of using e2e tests as API documentation, too. In that case, it may be necessary to re-order the tests. What's a good structure for such documentation? It could be organized by endpoint, then by method, like
- /domain
- POST
- GET
- DELETE
- /domain/{name}/rrsets/
- GET
- /domain/{name}/rrsets/{subname}/{type}
- POST
- single
- bulk
- GET
- POST
and so on; or it could be organized like the current documentation on desec.io, i.e. by use case.
In any event, some documentation for this bulk features is still missing from this PR.
Other than a couple of small notes, I did not discover significant problems with this, and I'd like to see it in master soon.
To be entirely honest, I'm not too familiar with the bulk features of the rest framework, so that part of the code I did not review in great detail, but more based on the the tests and common sense.
Awesome work, thanks!
api/desecapi/settings.py
Outdated
@@ -181,7 +181,7 @@ | |||
DEBUG = True | |||
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend' | |||
ABUSE_BY_REMOTE_IP_LIMIT = 100 | |||
ABUSE_BY_REMOTE_IP_PERIOD_HRS = 4800 | |||
ABUSE_BY_REMOTE_IP_PERIOD_HRS = 2 |
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'd say disable it for now, there are no tests that require the limit. It's better to have it definitely disabled than to have it go off at 100 different email addresses, causing random e2e test failures (as the order of the requests is not deterministic).
@@ -7,6 +7,7 @@ | |||
import datetime, uuid |
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 did not understand what you meant with 2.) dummy TTL for RRsets up for deletion
commit message
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.
If you send a PATCH
request with empty record contents []
, the RRset is supposed to be deleted. This works by sending a similar requests with an empty records
field to pdns. However, pdns requires the ttl
field to be set, but our PATCH
request reasonably does not require that. In this case, we set a dummy value.
(If you leave our the records
field from our PATCH
request, the RRset is not deleted; you can use such requests to change the TTL only.)
chakram.expect(chakram.resolveStr(queryName, type)).to.have.lengthOf(records.length); | ||
chakram.expect(chakram.resolveStr(queryName, type)).to.have.members(records); | ||
chakram.expect(chakram.resolveStr(queryName, type)).to.have.members(records.reverse()); | ||
if (typeof ttl !== "undefined") { |
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.
(no changes required, just commenting on javascript) typeof undefined === 'undefined'
😳
test/e2e/setup.js
Outdated
@@ -140,6 +140,7 @@ chakram.resolveStr = function (name, type) { | |||
case 'A': return data; | |||
case 'AAAA': return data; | |||
case 'MX': return data.preference + ' ' + data.exchange + '.'; | |||
case 'TXT': return '"' + data + '"'; |
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 put quotes around it? Quotes in the middle will not be escaped anyways
test/e2e/spec/api_spec.js
Outdated
var domain = 'e2etest-' + require("uuid").v4() + '.dedyn.io'; | ||
var response = chakram.get('/domains/' + domain + '/'); | ||
expect(response).to.have.status(200); | ||
//expect(response).to.have.schema(domainSchema); |
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 use that one? Remove and restructure this it
, or add it. (For one expectation, we don't need chakram.wait()
). Also, it's a misnomer
test/e2e/spec/api_spec.js
Outdated
before(function () { | ||
var response = chakram.delete('/domains/' + domain + '/rrsets/delete-test.../A/'); | ||
expect(response).to.have.status(204); | ||
return chakram.wait(); |
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.
return expect
is better (I believe), as it reduces the amount of promises we need to wait for
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.
shall do.
api/desecapi/views.py
Outdated
raise ex | ||
except ValidationError as e: | ||
if isinstance(e.detail, dict): | ||
all = e.detail.get('__all__') |
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.
shadows built-in all 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.
shall fix
1.) have write_rrsets() return all RRsets value (idempotence) Previously, only new or changed RRsets that were written to pdns had been returned. Now, all RRsets that exist at the end of the operation are returned (i.e. only the deleted ones are missing). 2.) dummy TTL for RRsets up for deletion 3.) fix deadlock by rephrasing WHERE clause for RR deletion 4.) refactor: simplify and clarify Domain.write_rrsets()
This commit also slightly shortens certain cache times for production, from default to 20s.
This commits refactors chakram.resolve() and introduces chakram.resolveStr(), both return promises that will eventually be resolved into response data from nslord. It also introduces dns, a dummy property to increase readability of expectations and ttl(exp), a function to check if a RR set has the correct ttl.
1.) remove unnecessary flags from DNS resolve code The following flags (i.e. all) were removed: - Recursion Desired: we don't want nslord to recurse - Authoritative Answer: reserved for responses, see RFC 1035 Sec. 4.1.1 2.) close socket after tests so that mocha will exit 3.) allow NXDOMAIN DNS response code This is a valid response code when looking for a non-existing RRset on a subdomain that has no records at all. 4.) add TXT record serialization to DNS toolset 5.) augment TTL test method
abd8509
to
c13f49d
Compare
Really? They have nothing to do with each other. Why do you think so?
Done, see c13f49d. I added this as a new commit for ease of review, but before merging, we should squash it into the main bulk e2e test commit. I also created another commit that adds testing of the JSON structure of responses of the
I agree that that would be nice to have, but I think we should open an issue for this instead of further holding up this PR.
Yes. As mentioned in one of the earlier comments, I postponed writing it until it is clear (after your review) that the interface will not change much. (Otherwise, the whole bulk docs would have had to be rewritten.) I'm working on this now, and I'd like to merge it in a separate PR. Are you ok with that?
Nice & thanks! I added replies to your code comments fixed some minor issues you pointed out. There are no outstanding issues from these comments. Pls let me know about any show-stoppers that I may have forgotten. Thanks! |
Just added the docs. A rendered version will be available for a few days at https://a4a.de/.u/1524251474_index.html |
293969b
to
1370e06
Compare
@nils-wisiol Here's a question regarding the docs. So far, our examples all have HTTPie commands because that tool doesn't require the user to know how to construct JSON objects. For the new bulk requests, however, HTTPie doesn't have a useful interface (see httpie/cli#121), so I added examples with curl. Ideally, it would be nice to add tabs to the examples to switch between the styles, but I don't want to spend the time to set this up. (Right now, we simply use |
@peterthomassen For me, the example section of an api documentation is one of the most important part to start. It usually allows you an very easy start into whatever you try to do. As curl is kind of a standard for testing and checking web services, my opinion is - curl is the must have in an api documentation. If you add more options, ... even better. I would not remove the httpie commands. But I understand that maintaining both might be a bit much. |
I extracted outstanding issues into
is there more to come? Anyhow, I'm ready to approve this. lgtm! :) |
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.
don't forget the squash the last two commits!
Wow. Thanks for the review!
Not for the moment.
Shall do, thanks for the reminder! |
Previously, IBAN and BIC were normalized and stripped from whitespace only for creating the direct debit attachment file. If the user entered spaces like ' DE123455....', those were stored in the database. Along with the 6-character cutoff, this was not desirable. Space stripping is now done in the Donation serializer so that it applies to both what's stored and to what's put in the direct debit file. Also, e2e tests were adapted to verify the presence of the masked IBAN.
OPT is a reserved record type with special meaning and is not supposed to exist in a zone. Currently, if an OPT RRset is created, the zone may break. Until PowerDNS ensures that RRsets of type OPT cannot created, let's make sure on this level. PowerDNS issue: PowerDNS/pdns#6441
pdns would convert types like TYPE99 to the named ones (here: SPF), but our API cannot do so without maintaining a mapping table that depends on the pdns version. Currently, we would store two distinct RRsets in our API database, corresponding to only one RRset in pdns. Worse, if one RRset was deleted (along with the one on pdns), the other would remain as an orphan. The only usecase for this would be new types quickly gaining popularity although pdns does not know them yet. In this case, we can address the issue again.
0ecf249
to
8060ac3
Compare
Finally!
A few notes:
I tried to make this a few separate commits, to avoid confusion. The main commit may still seem large (but it's a large feature, too). I can try to split it up a bit more if it is too clumsy, but the changes within it are a bit intertwined, so I'm not sure if it is really possible.
There are one or two unrelated commits that I didn't bother to make a separate PR for, as they are not urgent.
One of the e2e tests currently fails due to a minor problem; I'll try to fix it during the next days. I don't think that needs to hold back review.
e2e tests for RRset deletion via bulk requests (setting
'records': []
) are missing; I'll add them over the next days.e2e tests checking DNS output are mostly disabled (status "pending", with a dash) because the DNS library doesn't really give us what we want (not even TTLs, except for A/AAAA). We should try to find another DNS library.
Have fun, and 1000 thanks for reviewing! <3