Skip to content
This repository has been archived by the owner on Jul 16, 2021. It is now read-only.

Implement garbage collection for domains #1021

Merged
merged 14 commits into from
Jul 25, 2018

Conversation

gdbelvin
Copy link
Contributor

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.

@codecov
Copy link

codecov bot commented Jul 18, 2018

Codecov Report

Merging #1021 into master will increase coverage by 0.88%.
The diff coverage is 73.68%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
impl/sql/domain/storage.go 70.47% <100%> (+2.43%) ⬆️
core/adminserver/admin_server.go 67.4% <61.9%> (+0.52%) ⬆️
core/fake/domain_storage.go 85.71% <71.42%> (+85.71%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 122f7ab...d5f8702. Read the comment docs.


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

Choose a reason for hiding this comment

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

duration not a great name. Should be related to it's use.

// GarbageCollect request.
// Deletes domains that have been deleted longer than `duration`.
message GarbageCollectRequest {
// Domains soft-deleted older than duration will be fully deleted.
Copy link
Contributor

Choose a reason for hiding this comment

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

"older" -> "longer ago"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll change the name to before so the semantics can be a slightly simpler timestamp comparison.

// 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;
Copy link
Contributor

Choose a reason for hiding this comment

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

Same naming issue with duration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// See the License for the specific language governing permissions and
// limitations under the License.

package fake
Copy link
Contributor

Choose a reason for hiding this comment

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

Fake implementations are not usually tested. Perhaps this is more complex than a fake and should be renamed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fakes are indeed not usually tested. However:

  1. The tests did find and help correct a logic bug in the fake.
  2. All the untested fakes are pulling down the code coverage metrics :-)

Copy link
Contributor

Choose a reason for hiding this comment

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

All I'm really suggesting is that it's not really a fake. More of a dummy implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

@@ -41,25 +41,26 @@ CREATE TABLE IF NOT EXISTS Domains(
MinInterval BIGINT NOT NULL,
MaxInterval BIGINT NOT NULL,
Deleted INTEGER,
DeleteTimeMillis BIGINT,
DeleteTimeSeconds BIGINT,
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure why this changed? I'd suggest storing everything as nanos to avoid potential confusion later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was misnamed before. time.Unix() gives seconds, not Millis or Nanos.

Copy link
Contributor

Choose a reason for hiding this comment

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

Up to you but I still think this will go wrong at some point when it gets compared with some nano value somewhere..

Copy link
Contributor Author

@gdbelvin gdbelvin Jul 18, 2018

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

@gdbelvin
Copy link
Contributor Author

PTAL

Copy link
Contributor

@pav-kv pav-kv left a comment

Choose a reason for hiding this comment

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

Some comments.

}

message GarbageCollectResponse {
repeated Domain domains = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

How long can this list potentially be?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In current implementations, ~10. Maybe in the future it can grow to thousands.

Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM.

@@ -138,4 +150,8 @@ service KeyTransparencyAdmin {
delete: "/v1/domains/{domain_id}:undelete"
};
}

// Fully delete soft-deleted domains that have been deleted for longer than a duration.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: s/for longer than a duration/before the specified timestamp/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -46,4 +47,6 @@ type Storage interface {
Read(ctx context.Context, domainID string, showDeleted bool) (*Domain, error)
// Delete and undelete.
Copy link
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -33,7 +33,8 @@ type Domain struct {
VRFPriv proto.Message
MinInterval, MaxInterval time.Duration
// TODO(gbelvin): specify mutation function
Deleted bool
Deleted bool
DeletedTimestamp time.Time
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

@@ -55,6 +55,9 @@ func (a *DomainStorage) Read(ctx context.Context, ID string, showDeleted bool) (
if !ok {
return nil, status.Errorf(codes.NotFound, "Domain %v not found", ID)
}
if d.Deleted && !showDeleted {
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -149,7 +150,7 @@ func (s *storage) Write(ctx context.Context, d *domain.Domain) error {
d.MapID, d.LogID,
d.VRF.Der, anyData,
d.MinInterval.Nanoseconds(), d.MaxInterval.Nanoseconds(),
false)
false, time.Time{}.Unix())
Copy link
Contributor

Choose a reason for hiding this comment

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

As you mentioned above, this results in an undefined value (in go playground it's -62135596800). Maybe use zero instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

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 add a comment explaining that?

@@ -204,3 +209,9 @@ func (s *storage) SetDelete(ctx context.Context, domainID string, isDeleted bool
_, err := s.db.ExecContext(ctx, setDeletedSQL, isDeleted, time.Now().Unix(), domainID)
return err
}

// Delete deletes a domain.
func (s *storage) Delete(ctx context.Context, domainID string) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: permanently or hard-deletes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


func TestList(t *testing.T) {
ctx := context.Background()
admin, closeF := newStorage(t)
Copy link
Contributor

Choose a reason for hiding this comment

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

s/admin/s/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

for _, tc := range []struct {
domainID string
}{
{domainID: "test"},
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

Why can't you just append d instead of loading dproto?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

d just contains the log and map tree IDs. dproto replaces the treeID with the the trillian tree proto.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I see, thought they were the same protos.

Copy link
Contributor

@pav-kv pav-kv left a comment

Choose a reason for hiding this comment

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

LGTM % couple of nits. Please don't forget to regenerate files.

@@ -348,3 +348,37 @@ func (s *Server) DeleteDomain(ctx context.Context, in *pb.DeleteDomainRequest) (
func (s *Server) UndeleteDomain(ctx context.Context, in *pb.UndeleteDomainRequest) (*google_protobuf.Empty, error) {
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

s/for longer than a given duration/before the specified timestamp/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return nil, err
}

// Search for domains older than in.Duration.
Copy link
Contributor

Choose a reason for hiding this comment

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

s/older than in.Duration/deleted earlier than in.Before/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -255,6 +255,17 @@ func TestDelete(t *testing.T) {
if got, want := domain.Map.Deleted, true; got != want {
t.Errorf("Map.TreeState: %v, want %v", got, want)
}
// Garbage collect
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Redundant comment, the next line is GarbageCollect.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -138,4 +150,8 @@ service KeyTransparencyAdmin {
delete: "/v1/domains/{domain_id}:undelete"
};
}

// Fully delete soft-deleted domains that have been deleted before the specified timestamp.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Fully delete the domains that have been soft-deleted before the specified timestamp.

@gdbelvin gdbelvin merged commit d205da2 into google:master Jul 25, 2018
@gdbelvin gdbelvin deleted the f/garbagecollect branch July 25, 2018 13:01
gdbelvin added a commit to gdbelvin/keytransparency that referenced this pull request Jul 25, 2018
* 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)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants