services/content: move service client into package
Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
parent
4793f968e5
commit
5da4e1d0d2
10 changed files with 136 additions and 102 deletions
4
cmd/dist/get.go
vendored
4
cmd/dist/get.go
vendored
|
@ -5,7 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
contentapi "github.com/docker/containerd/api/services/content"
|
contentapi "github.com/docker/containerd/api/services/content"
|
||||||
"github.com/docker/containerd/content"
|
contentservice "github.com/docker/containerd/services/content"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
@ -33,7 +33,7 @@ Output paths can be used to directly access blobs on disk.`,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := content.NewProviderFromClient(contentapi.NewContentClient(conn))
|
cs := contentservice.NewProviderFromClient(contentapi.NewContentClient(conn))
|
||||||
|
|
||||||
rc, err := cs.Reader(ctx, dgst)
|
rc, err := cs.Reader(ctx, dgst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
5
cmd/dist/ingest.go
vendored
5
cmd/dist/ingest.go
vendored
|
@ -6,9 +6,10 @@ import (
|
||||||
|
|
||||||
contentapi "github.com/docker/containerd/api/services/content"
|
contentapi "github.com/docker/containerd/api/services/content"
|
||||||
"github.com/docker/containerd/content"
|
"github.com/docker/containerd/content"
|
||||||
|
contentservice "github.com/docker/containerd/services/content"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/urfave/cli"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ingestCommand = cli.Command{
|
var ingestCommand = cli.Command{
|
||||||
|
@ -51,7 +52,7 @@ var ingestCommand = cli.Command{
|
||||||
return errors.New("must specify a transaction reference")
|
return errors.New("must specify a transaction reference")
|
||||||
}
|
}
|
||||||
|
|
||||||
ingester := content.NewIngesterFromClient(contentapi.NewContentClient(conn))
|
ingester := contentservice.NewIngesterFromClient(contentapi.NewContentClient(conn))
|
||||||
|
|
||||||
// TODO(stevvooe): Allow ingest to be reentrant. Currently, we expect
|
// TODO(stevvooe): Allow ingest to be reentrant. Currently, we expect
|
||||||
// all data to be written in a single invocation. Allow multiple writes
|
// all data to be written in a single invocation. Allow multiple writes
|
||||||
|
|
|
@ -11,10 +11,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNotFound = errors.New("content: not found")
|
// ErrNotFound is returned when an item is not found.
|
||||||
errExists = errors.New("content: exists")
|
//
|
||||||
|
// Use IsNotFound(err) to detect this condition.
|
||||||
|
ErrNotFound = errors.New("content: not found")
|
||||||
|
|
||||||
BufPool = sync.Pool{
|
// ErrExists is returned when something exists when it may not be expected.
|
||||||
|
//
|
||||||
|
// Use IsExists(err) to detect this condition.
|
||||||
|
ErrExists = errors.New("content: exists")
|
||||||
|
|
||||||
|
bufPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
return make([]byte, 1<<20)
|
return make([]byte, 1<<20)
|
||||||
},
|
},
|
||||||
|
@ -52,9 +59,9 @@ type Ingester interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsNotFound(err error) bool {
|
func IsNotFound(err error) bool {
|
||||||
return errors.Cause(err) == errNotFound
|
return errors.Cause(err) == ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsExists(err error) bool {
|
func IsExists(err error) bool {
|
||||||
return errors.Cause(err) == errExists
|
return errors.Cause(err) == ErrExists
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,8 @@ func WriteBlob(ctx context.Context, cs Ingester, ref string, r io.Reader, size i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := BufPool.Get().([]byte)
|
buf := bufPool.Get().([]byte)
|
||||||
defer BufPool.Put(buf)
|
defer bufPool.Put(buf)
|
||||||
|
|
||||||
if _, err := io.CopyBuffer(cw, r, buf); err != nil {
|
if _, err := io.CopyBuffer(cw, r, buf); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -41,7 +41,7 @@ func (s *Store) Info(dgst digest.Digest) (Info, error) {
|
||||||
fi, err := os.Stat(p)
|
fi, err := os.Stat(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
err = errNotFound
|
err = ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return Info{}, err
|
return Info{}, err
|
||||||
|
@ -63,7 +63,7 @@ func (s *Store) Reader(ctx context.Context, dgst digest.Digest) (io.ReadCloser,
|
||||||
fp, err := os.Open(s.blobPath(dgst))
|
fp, err := os.Open(s.blobPath(dgst))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
err = errNotFound
|
err = ErrNotFound
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func (cs *Store) Delete(dgst digest.Digest) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errNotFound
|
return ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -243,8 +243,8 @@ func (s *Store) Writer(ctx context.Context, ref string, total int64, expected di
|
||||||
}
|
}
|
||||||
defer fp.Close()
|
defer fp.Close()
|
||||||
|
|
||||||
p := BufPool.Get().([]byte)
|
p := bufPool.Get().([]byte)
|
||||||
defer BufPool.Put(p)
|
defer bufPool.Put(p)
|
||||||
|
|
||||||
offset, err = io.CopyBuffer(digester.Hash(), fp, p)
|
offset, err = io.CopyBuffer(digester.Hash(), fp, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -102,7 +102,7 @@ func (w *writer) Commit(size int64, expected digest.Digest) error {
|
||||||
if err := os.Rename(ingest, target); err != nil {
|
if err := os.Rename(ingest, target); err != nil {
|
||||||
if os.IsExist(err) {
|
if os.IsExist(err) {
|
||||||
// collision with the target file!
|
// collision with the target file!
|
||||||
return errExists
|
return ErrExists
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
19
services/content/helpers.go
Normal file
19
services/content/helpers.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package content
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/containerd/content"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func rewriteGRPCError(err error) error {
|
||||||
|
switch grpc.Code(errors.Cause(err)) {
|
||||||
|
case codes.AlreadyExists:
|
||||||
|
return content.ErrExists
|
||||||
|
case codes.NotFound:
|
||||||
|
return content.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -4,79 +4,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
|
|
||||||
contentapi "github.com/docker/containerd/api/services/content"
|
contentapi "github.com/docker/containerd/api/services/content"
|
||||||
|
"github.com/docker/containerd/content"
|
||||||
|
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewProviderFromClient(client contentapi.ContentClient) Provider {
|
func NewIngesterFromClient(client contentapi.ContentClient) content.Ingester {
|
||||||
return &remoteProvider{
|
|
||||||
client: client,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type remoteProvider struct {
|
|
||||||
client contentapi.ContentClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *remoteProvider) Reader(ctx context.Context, dgst digest.Digest) (io.ReadCloser, error) {
|
|
||||||
client, err := rp.client.Read(ctx, &contentapi.ReadRequest{Digest: dgst})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &remoteReader{
|
|
||||||
client: client,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type remoteReader struct {
|
|
||||||
client contentapi.Content_ReadClient
|
|
||||||
extra []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *remoteReader) Read(p []byte) (n int, err error) {
|
|
||||||
n += copy(p, rr.extra)
|
|
||||||
if n >= len(p) {
|
|
||||||
if n <= len(rr.extra) {
|
|
||||||
rr.extra = rr.extra[n:]
|
|
||||||
} else {
|
|
||||||
rr.extra = rr.extra[:0]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p = p[n:]
|
|
||||||
for len(p) > 0 {
|
|
||||||
var resp *contentapi.ReadResponse
|
|
||||||
// fill our buffer up until we can fill p.
|
|
||||||
resp, err = rr.client.Recv()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
copied := copy(p, resp.Data)
|
|
||||||
n += copied
|
|
||||||
p = p[copied:]
|
|
||||||
|
|
||||||
if copied < len(p) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rr.extra = append(rr.extra, resp.Data[copied:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *remoteReader) Close() error {
|
|
||||||
return rr.client.CloseSend()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIngesterFromClient(client contentapi.ContentClient) Ingester {
|
|
||||||
return &remoteIngester{
|
return &remoteIngester{
|
||||||
client: client,
|
client: client,
|
||||||
}
|
}
|
||||||
|
@ -86,7 +21,7 @@ type remoteIngester struct {
|
||||||
client contentapi.ContentClient
|
client contentapi.ContentClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ri *remoteIngester) Writer(ctx context.Context, ref string, size int64, expected digest.Digest) (Writer, error) {
|
func (ri *remoteIngester) Writer(ctx context.Context, ref string, size int64, expected digest.Digest) (content.Writer, error) {
|
||||||
wrclient, offset, err := ri.negotiate(ctx, ref, size, expected)
|
wrclient, offset, err := ri.negotiate(ctx, ref, size, expected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, rewriteGRPCError(err)
|
return nil, rewriteGRPCError(err)
|
||||||
|
@ -154,15 +89,15 @@ func (rw *remoteWriter) send(req *contentapi.WriteRequest) (*contentapi.WriteRes
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *remoteWriter) Status() (Status, error) {
|
func (rw *remoteWriter) Status() (content.Status, error) {
|
||||||
resp, err := rw.send(&contentapi.WriteRequest{
|
resp, err := rw.send(&contentapi.WriteRequest{
|
||||||
Action: contentapi.WriteActionStat,
|
Action: contentapi.WriteActionStat,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Status{}, err
|
return content.Status{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Status{
|
return content.Status{
|
||||||
Ref: rw.ref,
|
Ref: rw.ref,
|
||||||
Offset: resp.Offset,
|
Offset: resp.Offset,
|
||||||
StartedAt: resp.StartedAt,
|
StartedAt: resp.StartedAt,
|
||||||
|
@ -226,14 +161,3 @@ func (rw *remoteWriter) Truncate(size int64) error {
|
||||||
func (rw *remoteWriter) Close() error {
|
func (rw *remoteWriter) Close() error {
|
||||||
return rw.client.CloseSend()
|
return rw.client.CloseSend()
|
||||||
}
|
}
|
||||||
|
|
||||||
func rewriteGRPCError(err error) error {
|
|
||||||
switch grpc.Code(errors.Cause(err)) {
|
|
||||||
case codes.AlreadyExists:
|
|
||||||
return errExists
|
|
||||||
case codes.NotFound:
|
|
||||||
return errNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
76
services/content/provider.go
Normal file
76
services/content/provider.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package content
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
contentapi "github.com/docker/containerd/api/services/content"
|
||||||
|
"github.com/docker/containerd/content"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewProviderFromClient(client contentapi.ContentClient) content.Provider {
|
||||||
|
return &remoteProvider{
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type remoteProvider struct {
|
||||||
|
client contentapi.ContentClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *remoteProvider) Reader(ctx context.Context, dgst digest.Digest) (io.ReadCloser, error) {
|
||||||
|
client, err := rp.client.Read(ctx, &contentapi.ReadRequest{Digest: dgst})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &remoteReader{
|
||||||
|
client: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type remoteReader struct {
|
||||||
|
client contentapi.Content_ReadClient
|
||||||
|
extra []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *remoteReader) Read(p []byte) (n int, err error) {
|
||||||
|
n += copy(p, rr.extra)
|
||||||
|
if n >= len(p) {
|
||||||
|
if n <= len(rr.extra) {
|
||||||
|
rr.extra = rr.extra[n:]
|
||||||
|
} else {
|
||||||
|
rr.extra = rr.extra[:0]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p[n:]
|
||||||
|
for len(p) > 0 {
|
||||||
|
var resp *contentapi.ReadResponse
|
||||||
|
// fill our buffer up until we can fill p.
|
||||||
|
resp, err = rr.client.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
copied := copy(p, resp.Data)
|
||||||
|
n += copied
|
||||||
|
p = p[copied:]
|
||||||
|
|
||||||
|
if copied < len(p) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.extra = append(rr.extra, resp.Data[copied:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stevvooe): Implemente io.ReaderAt.
|
||||||
|
|
||||||
|
func (rr *remoteReader) Close() error {
|
||||||
|
return rr.client.CloseSend()
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package content
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/containerd"
|
"github.com/docker/containerd"
|
||||||
|
@ -20,6 +21,12 @@ type Service struct {
|
||||||
store *content.Store
|
store *content.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return make([]byte, 1<<20)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var _ api.ContentServer = &Service{}
|
var _ api.ContentServer = &Service{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -100,9 +107,9 @@ func (s *Service) Read(req *api.ReadRequest, session api.Content_ReadServer) err
|
||||||
|
|
||||||
// TODO(stevvooe): Using the global buffer pool. At 32KB, it is probably
|
// TODO(stevvooe): Using the global buffer pool. At 32KB, it is probably
|
||||||
// little inefficient for work over a fast network. We can tune this later.
|
// little inefficient for work over a fast network. We can tune this later.
|
||||||
p = content.BufPool.Get().([]byte)
|
p = bufPool.Get().([]byte)
|
||||||
)
|
)
|
||||||
defer content.BufPool.Put(p)
|
defer bufPool.Put(p)
|
||||||
|
|
||||||
if offset < 0 {
|
if offset < 0 {
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
Loading…
Reference in a new issue