Merge pull request #576 from stevvooe/seek-reset-handling
content: allow reset via Truncate
This commit is contained in:
commit
317b884110
7 changed files with 64 additions and 13 deletions
|
@ -197,8 +197,14 @@ type WriteRequest struct {
|
|||
// with the commit action message.
|
||||
Expected github_com_opencontainers_go_digest.Digest `protobuf:"bytes,4,opt,name=expected,proto3,customtype=github.com/opencontainers/go-digest.Digest" json:"expected"`
|
||||
// Offset specifies the number of bytes from the start at which to begin
|
||||
// the write. If zero or less, the write will be from the start. This uses
|
||||
// standard zero-indexed semantics.
|
||||
// the write. For most implementations, this means from the start of the
|
||||
// file. This uses standard, zero-indexed semantics.
|
||||
//
|
||||
// If the action is write, the remote may remove all previously written
|
||||
// data up to the offset. Implementations may support arbitrary offsets but
|
||||
// MUST support reseting this value to zero with with a write. If an
|
||||
// implementation does not support a write at a particular offset, an
|
||||
// OutOfRange error must be returned.
|
||||
Offset int64 `protobuf:"varint,5,opt,name=offset,proto3" json:"offset,omitempty"`
|
||||
// Data is the actual bytes to be written.
|
||||
//
|
||||
|
|
|
@ -95,10 +95,10 @@ message ReadResponse {
|
|||
enum WriteAction {
|
||||
option (gogoproto.goproto_enum_prefix) = false;
|
||||
option (gogoproto.enum_customname) = "WriteAction";
|
||||
|
||||
|
||||
// WriteActionStat instructs the writer to return the current status while
|
||||
// holding the lock on the write.
|
||||
STAT = 0 [(gogoproto.enumvalue_customname)="WriteActionStat"];
|
||||
STAT = 0 [(gogoproto.enumvalue_customname) = "WriteActionStat"];
|
||||
|
||||
// WriteActionWrite sets the action for the write request to write data.
|
||||
//
|
||||
|
@ -106,7 +106,7 @@ enum WriteAction {
|
|||
// transaction will be left open for further writes.
|
||||
//
|
||||
// This is the default.
|
||||
WRITE = 1 [(gogoproto.enumvalue_customname)="WriteActionWrite"];
|
||||
WRITE = 1 [(gogoproto.enumvalue_customname) = "WriteActionWrite"];
|
||||
|
||||
// WriteActionCommit will write any outstanding data in the message and
|
||||
// commit the write, storing it under the digest.
|
||||
|
@ -115,13 +115,13 @@ enum WriteAction {
|
|||
// commit it.
|
||||
//
|
||||
// This action will always terminate the write.
|
||||
COMMIT = 2 [(gogoproto.enumvalue_customname)="WriteActionCommit"];
|
||||
COMMIT = 2 [(gogoproto.enumvalue_customname) = "WriteActionCommit"];
|
||||
|
||||
// WriteActionAbort will release any resources associated with the write
|
||||
// and free up the ref for a completely new set of writes.
|
||||
//
|
||||
// This action will always terminate the write.
|
||||
ABORT = -1 [(gogoproto.enumvalue_customname)="WriteActionAbort"];
|
||||
ABORT = -1 [(gogoproto.enumvalue_customname) = "WriteActionAbort"];
|
||||
}
|
||||
|
||||
// WriteRequest writes data to the request ref at offset.
|
||||
|
@ -165,8 +165,14 @@ message WriteRequest {
|
|||
string expected = 4 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
|
||||
|
||||
// Offset specifies the number of bytes from the start at which to begin
|
||||
// the write. If zero or less, the write will be from the start. This uses
|
||||
// standard zero-indexed semantics.
|
||||
// the write. For most implementations, this means from the start of the
|
||||
// file. This uses standard, zero-indexed semantics.
|
||||
//
|
||||
// If the action is write, the remote may remove all previously written
|
||||
// data after the offset. Implementations may support arbitrary offsets but
|
||||
// MUST support reseting this value to zero with a write. If an
|
||||
// implementation does not support a write at a particular offset, an
|
||||
// OutOfRange error must be returned.
|
||||
int64 offset = 5;
|
||||
|
||||
// Data is the actual bytes to be written.
|
||||
|
|
|
@ -199,6 +199,7 @@ func (rw *remoteWriter) Commit(size int64, expected digest.Digest) error {
|
|||
resp, err := rw.send(&contentapi.WriteRequest{
|
||||
Action: contentapi.WriteActionCommit,
|
||||
Total: size,
|
||||
Offset: rw.offset,
|
||||
Expected: expected,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -216,6 +217,12 @@ func (rw *remoteWriter) Commit(size int64, expected digest.Digest) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (rw *remoteWriter) Truncate(size int64) error {
|
||||
// This truncation won't actually be validated until a write is issued.
|
||||
rw.offset = size
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rw *remoteWriter) Close() error {
|
||||
return rw.client.CloseSend()
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ type Writer interface {
|
|||
Status() (Status, error)
|
||||
Digest() digest.Digest
|
||||
Commit(size int64, expected digest.Digest) error
|
||||
Truncate(size int64) error
|
||||
}
|
||||
|
||||
type Ingester interface {
|
||||
|
|
|
@ -36,7 +36,14 @@ func WriteBlob(ctx context.Context, cs Ingester, ref string, r io.Reader, size i
|
|||
if ws.Offset > 0 {
|
||||
r, err = seekReader(r, ws.Offset, size)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unabled to resume write to %v", ref)
|
||||
if !isUnseekable(err) {
|
||||
return errors.Wrapf(err, "unabled to resume write to %v", ref)
|
||||
}
|
||||
|
||||
// reader is unseekable, try to move the writer back to the start.
|
||||
if err := cw.Truncate(0); err != nil {
|
||||
return errors.Wrapf(err, "content writer truncate failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,13 +56,19 @@ func WriteBlob(ctx context.Context, cs Ingester, ref string, r io.Reader, size i
|
|||
|
||||
if err := cw.Commit(size, expected); err != nil {
|
||||
if !IsExists(err) {
|
||||
return err
|
||||
return errors.Wrapf(err, "failed commit on ref %q", ref)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var errUnseekable = errors.New("seek not supported")
|
||||
|
||||
func isUnseekable(err error) bool {
|
||||
return errors.Cause(err) == errUnseekable
|
||||
}
|
||||
|
||||
// seekReader attempts to seek the reader to the given offset, either by
|
||||
// resolving `io.Seeker` or by detecting `io.ReaderAt`.
|
||||
func seekReader(r io.Reader, offset, size int64) (io.Reader, error) {
|
||||
|
@ -81,7 +94,7 @@ func seekReader(r io.Reader, offset, size int64) (io.Reader, error) {
|
|||
return sr, nil
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("cannot seek to offset %v", offset)
|
||||
return r, errors.Wrapf(errUnseekable, "seek to offset %v failed", offset)
|
||||
}
|
||||
|
||||
func readFileString(path string) (string, error) {
|
||||
|
|
|
@ -74,7 +74,7 @@ func (w *writer) Commit(size int64, expected digest.Digest) error {
|
|||
}
|
||||
|
||||
if size > 0 && size != fi.Size() {
|
||||
return errors.Errorf("failed size validation: %v != %v", fi.Size(), size)
|
||||
return errors.Errorf("%q failed size validation: %v != %v", w.ref, fi.Size(), size)
|
||||
}
|
||||
|
||||
if err := w.fp.Close(); err != nil {
|
||||
|
@ -133,3 +133,12 @@ func (cw *writer) Close() (err error) {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *writer) Truncate(size int64) error {
|
||||
if size != 0 {
|
||||
return errors.New("Truncate: unsupported size")
|
||||
}
|
||||
w.offset = 0
|
||||
w.digester.Hash().Reset()
|
||||
return w.fp.Truncate(0)
|
||||
}
|
||||
|
|
|
@ -195,6 +195,8 @@ func (s *Service) Write(session api.Content_WriteServer) (err error) {
|
|||
}
|
||||
|
||||
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(fields))
|
||||
|
||||
log.G(ctx).Debug("(*Service).Write started")
|
||||
// this action locks the writer for the session.
|
||||
wr, err := s.store.Writer(ctx, ref, total, expected)
|
||||
if err != nil {
|
||||
|
@ -266,6 +268,13 @@ func (s *Service) Write(session api.Content_WriteServer) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if req.Offset == 0 && ws.Offset > 0 {
|
||||
if err := wr.Truncate(req.Offset); err != nil {
|
||||
return errors.Wrapf(err, "truncate failed")
|
||||
}
|
||||
msg.Offset = req.Offset
|
||||
}
|
||||
|
||||
// issue the write if we actually have data.
|
||||
if len(req.Data) > 0 {
|
||||
// While this looks like we could use io.WriterAt here, because we
|
||||
|
|
Loading…
Reference in a new issue