Implement garbage collection for domains#1021
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1021 +/- ##
==========================================
+ Coverage 65.12% 66.01% +0.88%
==========================================
Files 39 39
Lines 2707 2742 +35
==========================================
+ Hits 1763 1810 +47
+ Misses 630 613 -17
- Partials 314 319 +5
Continue to review full report at Codecov.
|
core/adminserver/admin_server.go
Outdated
|
|
||
| // GarbageCollect looks for domains that have been deleted for longer than a given duration and fully deletes them. | ||
| func (s *Server) GarbageCollect(ctx context.Context, in *pb.GarbageCollectRequest) (*pb.GarbageCollectResponse, error) { | ||
| duration, err := ptypes.Duration(in.GetDuration()) |
There was a problem hiding this comment.
duration not a great name. Should be related to it's use.
core/api/v1/admin.proto
Outdated
| // GarbageCollect request. | ||
| // Deletes domains that have been deleted longer than `duration`. | ||
| message GarbageCollectRequest { | ||
| // Domains soft-deleted older than duration will be fully deleted. |
There was a problem hiding this comment.
"older" -> "longer ago"
There was a problem hiding this comment.
I'll change the name to before so the semantics can be a slightly simpler timestamp comparison.
core/api/v1/admin.proto
Outdated
| // Deletes domains that have been deleted longer than `duration`. | ||
| message GarbageCollectRequest { | ||
| // Domains soft-deleted older than duration will be fully deleted. | ||
| google.protobuf.Duration duration = 1; |
There was a problem hiding this comment.
Same naming issue with duration.
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package fake |
There was a problem hiding this comment.
Fake implementations are not usually tested. Perhaps this is more complex than a fake and should be renamed?
There was a problem hiding this comment.
Fakes are indeed not usually tested. However:
- The tests did find and help correct a logic bug in the fake.
- All the untested fakes are pulling down the code coverage metrics :-)
There was a problem hiding this comment.
All I'm really suggesting is that it's not really a fake. More of a dummy implementation.
There was a problem hiding this comment.
Well, that's also the definition of a fake. Ideally I would just use the real mysql implementation, but I've structured the repo such that things in core shouldn't have outgoing references to implementation specific things in impl
| MaxInterval BIGINT NOT NULL, | ||
| Deleted INTEGER, | ||
| DeleteTimeMillis BIGINT, | ||
| DeleteTimeSeconds BIGINT, |
There was a problem hiding this comment.
Not sure why this changed? I'd suggest storing everything as nanos to avoid potential confusion later.
There was a problem hiding this comment.
It was misnamed before. time.Unix() gives seconds, not Millis or Nanos.
There was a problem hiding this comment.
Up to you but I still think this will go wrong at some point when it gets compared with some nano value somewhere..
There was a problem hiding this comment.
I could use UnixNanos, but this leaves the zero time object undefined. Is there a reason you think that second granularity could be insufficient?
https://godoc.org/time#Time.UnixNano:
UnixNano returns t as a Unix time, the number of nanoseconds elapsed since January 1, 1970 UTC. The result is undefined if the Unix time in nanoseconds cannot be represented by an int64 (a date before the year 1678 or after 2262). Note that this means the result of calling UnixNano on the zero Time is undefined.
There was a problem hiding this comment.
I second Martin that second precision is a bit wacky, but I also buy the "undefined" argument. I think it's okay to leave as is for the purposes of this PR.
|
PTAL |
| } | ||
|
|
||
| message GarbageCollectResponse { | ||
| repeated Domain domains = 1; |
There was a problem hiding this comment.
How long can this list potentially be?
There was a problem hiding this comment.
In current implementations, ~10. Maybe in the future it can grow to thousands.
core/api/v1/admin.proto
Outdated
| }; | ||
| } | ||
|
|
||
| // Fully delete soft-deleted domains that have been deleted for longer than a duration. |
There was a problem hiding this comment.
nit: s/for longer than a duration/before the specified timestamp/
core/domain/storage.go
Outdated
| @@ -46,4 +47,6 @@ type Storage interface { | |||
| Read(ctx context.Context, domainID string, showDeleted bool) (*Domain, error) | |||
| // Delete and undelete. | |||
There was a problem hiding this comment.
While you are here, could you reword this comment? and looks confusing to me, maybe or? Could be also something like "Soft-delete or undelete the domain."
| // TODO(gbelvin): specify mutation function | ||
| Deleted bool | ||
| Deleted bool | ||
| DeletedTimestamp time.Time |
There was a problem hiding this comment.
Just an idea: Maybe we could use DeletedTimestamp.IsZero() as an indicator instead of explicit bool Deleted.
time.Time doc says:
The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC. As this time is unlikely to come up in practice, the IsZero method gives a simple way of detecting a time that has not been initialized explicitly.
There was a problem hiding this comment.
IsZero works in Go, but it's harder to do a SQL query on.
Also, given the somewhat fragile nature of the serialization of the zero date, a boolean is safer.
core/fake/domain_storage.go
Outdated
| if !ok { | ||
| return nil, status.Errorf(codes.NotFound, "Domain %v not found", ID) | ||
| } | ||
| if d.Deleted && !showDeleted { |
There was a problem hiding this comment.
personal taste nit: How about?
if !ok || d.Deleted && !showDeleted {
return nil, status.Errorf(codes.NotFound, "Domain %v not found", ID)
}
... to avoid copy-pasting the error.
impl/sql/domain/storage.go
Outdated
| d.VRF.Der, anyData, | ||
| d.MinInterval.Nanoseconds(), d.MaxInterval.Nanoseconds(), | ||
| false) | ||
| false, time.Time{}.Unix()) |
There was a problem hiding this comment.
As you mentioned above, this results in an undefined value (in go playground it's -62135596800). Maybe use zero instead?
There was a problem hiding this comment.
This is a bit subtle:
This is the zero value for time.Time. When represented as unix seconds, we get -62135596800. This is small enough that it fits in an int64 and is therefore defined. If we were to use UnixNanos, the number would underflow and be undefined.
There was a problem hiding this comment.
Could you add a comment explaining that?
| } | ||
|
|
||
| // Delete deletes a domain. | ||
| func (s *storage) Delete(ctx context.Context, domainID string) error { |
There was a problem hiding this comment.
nit: permanently or hard-deletes
impl/sql/domain/storage_test.go
Outdated
|
|
||
| func TestList(t *testing.T) { | ||
| ctx := context.Background() | ||
| admin, closeF := newStorage(t) |
| for _, tc := range []struct { | ||
| domainID string | ||
| }{ | ||
| {domainID: "test"}, |
There was a problem hiding this comment.
Same thing here with table-driven test. Either make it not such, or add some more cases. I think one more case can test the showDeleted flag of Read.
There was a problem hiding this comment.
See comment above about table tests even for one case.
| if err := s.domains.Delete(ctx, d.DomainID); err != nil { | ||
| return nil, err | ||
| } | ||
| deleted = append(deleted, dproto) |
There was a problem hiding this comment.
Why can't you just append d instead of loading dproto?
There was a problem hiding this comment.
d just contains the log and map tree IDs. dproto replaces the treeID with the the trillian tree proto.
There was a problem hiding this comment.
Oh I see, thought they were the same protos.
…y into f/garbagecollect * 'f/garbagecollect' of github.com:gdbelvin/keytransparency: Remove service-key (google#1022)
pav-kv
left a comment
There was a problem hiding this comment.
LGTM % couple of nits. Please don't forget to regenerate files.
core/adminserver/admin_server.go
Outdated
| return nil, status.Errorf(codes.Unimplemented, "not implemented") | ||
| } | ||
|
|
||
| // GarbageCollect looks for domains that have been deleted for longer than a given duration and fully deletes them. |
There was a problem hiding this comment.
s/for longer than a given duration/before the specified timestamp/
core/adminserver/admin_server.go
Outdated
| return nil, err | ||
| } | ||
|
|
||
| // Search for domains older than in.Duration. |
There was a problem hiding this comment.
s/older than in.Duration/deleted earlier than in.Before/
| if got, want := domain.Map.Deleted, true; got != want { | ||
| t.Errorf("Map.TreeState: %v, want %v", got, want) | ||
| } | ||
| // Garbage collect |
There was a problem hiding this comment.
nit: Redundant comment, the next line is GarbageCollect.
core/api/v1/admin.proto
Outdated
| }; | ||
| } | ||
|
|
||
| // Fully delete soft-deleted domains that have been deleted before the specified timestamp. |
There was a problem hiding this comment.
nit: Fully delete the domains that have been soft-deleted before the specified timestamp.
* master: Implement garbage collection for domains (google#1021) Remove TRILLIAN_MYSQL_DRIVER (google#1023) Rename gometalinter gas to gosec (google#1024) Remove service-key (google#1022)
Currently each test is started with the benefit of a brand new database for maximal test isolation.
However, this is also wasteful, and also not possible in all testing environments. Servers -- and tests should cleanup after themselves. This PR adds the cleanup features needed.