forked from TrueCloudLab/frostfs-http-gw
[#19] Add a version with no cdn-sdk deps
Signed-off-by: Pavel Korotkov <pavel@nspcc.ru>
This commit is contained in:
parent
cdab794d62
commit
4c96885a42
20 changed files with 930 additions and 266 deletions
286
neofs/client-plant.go
Normal file
286
neofs/client-plant.go
Normal file
|
@ -0,0 +1,286 @@
|
|||
package neofs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/client"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/owner"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/token"
|
||||
objectCore "github.com/nspcc-dev/neofs-node/pkg/core/object"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
nodeConnectionTimeout = 10 * time.Second
|
||||
maxObjectSize = uint64(1 << (20 + 6)) // 64MB
|
||||
)
|
||||
|
||||
type PutOptions struct {
|
||||
Client client.Client
|
||||
SessionToken *token.SessionToken
|
||||
BearerToken *token.BearerToken
|
||||
// ...
|
||||
ContainerID *container.ID
|
||||
OwnerID *owner.ID
|
||||
PrepareObjectOnsite bool
|
||||
Reader io.Reader
|
||||
}
|
||||
|
||||
type GetOptions struct {
|
||||
Client client.Client
|
||||
SessionToken *token.SessionToken
|
||||
BearerToken *token.BearerToken
|
||||
// ...
|
||||
ObjectAddress *object.Address
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
type SearchOptions struct {
|
||||
Client client.Client
|
||||
SessionToken *token.SessionToken
|
||||
BearerToken *token.BearerToken
|
||||
// ...
|
||||
ContainerID *container.ID
|
||||
Attribute struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteOptions struct {
|
||||
Client client.Client
|
||||
SessionToken *token.SessionToken
|
||||
BearerToken *token.BearerToken
|
||||
// ...
|
||||
ObjectAddress *object.Address
|
||||
}
|
||||
|
||||
type ObjectClient interface {
|
||||
Put(context.Context, *PutOptions) (*object.Address, error)
|
||||
Get(context.Context, *GetOptions) (*object.Object, error)
|
||||
Search(context.Context, *SearchOptions) ([]*object.ID, error)
|
||||
Delete(context.Context, *DeleteOptions) error
|
||||
}
|
||||
|
||||
type ClientPlant interface {
|
||||
GetReusableArtifacts(ctx context.Context) (client.Client, *token.SessionToken, error)
|
||||
Object() ObjectClient
|
||||
OwnerID() *owner.ID
|
||||
}
|
||||
|
||||
type objectClient struct {
|
||||
key *ecdsa.PrivateKey
|
||||
conn *grpc.ClientConn
|
||||
}
|
||||
|
||||
type neofsClient struct {
|
||||
key *ecdsa.PrivateKey
|
||||
ownerID *owner.ID
|
||||
conn *grpc.ClientConn
|
||||
}
|
||||
|
||||
func (cc *neofsClient) GetReusableArtifacts(ctx context.Context) (client.Client, *token.SessionToken, error) {
|
||||
c, err := client.New(client.WithDefaultPrivateKey(cc.key), client.WithGRPCConnection(cc.conn))
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to create reusable neofs client")
|
||||
}
|
||||
st, err := c.CreateSession(ctx, math.MaxUint64)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to create reusable neofs session token")
|
||||
}
|
||||
return c, st, nil
|
||||
}
|
||||
|
||||
func (cc *neofsClient) Object() ObjectClient {
|
||||
return &objectClient{key: cc.key, conn: cc.conn}
|
||||
}
|
||||
|
||||
func (cc *neofsClient) OwnerID() *owner.ID {
|
||||
return cc.ownerID
|
||||
}
|
||||
|
||||
func NewClientPlant(ctx context.Context, address string, creds Credentials) (ClientPlant, error) {
|
||||
toctx, c := context.WithTimeout(ctx, nodeConnectionTimeout)
|
||||
defer c()
|
||||
conn, err := grpc.DialContext(toctx, address, grpc.WithInsecure(), grpc.WithBlock())
|
||||
if err != nil {
|
||||
if err == context.DeadlineExceeded {
|
||||
err = errors.New("failed to connect to neofs node")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &neofsClient{
|
||||
key: creds.PrivateKey(),
|
||||
ownerID: creds.Owner(),
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (oc *objectClient) Put(ctx context.Context, options *PutOptions) (*object.Address, error) {
|
||||
var (
|
||||
err error
|
||||
objectID *object.ID
|
||||
)
|
||||
address := object.NewAddress()
|
||||
if options.PrepareObjectOnsite {
|
||||
rawObject := objectCore.NewRaw()
|
||||
rawObject.SetContainerID(options.ContainerID)
|
||||
rawObject.SetOwnerID(options.OwnerID)
|
||||
ns := newNetworkState(ctx, options.Client)
|
||||
objectTarget := transformer.NewPayloadSizeLimiter(maxObjectSize, func() transformer.ObjectTarget {
|
||||
return transformer.NewFormatTarget(&transformer.FormatterParams{
|
||||
Key: oc.key,
|
||||
NextTarget: &remoteClientTarget{
|
||||
ctx: ctx,
|
||||
client: options.Client,
|
||||
},
|
||||
NetworkState: ns,
|
||||
})
|
||||
})
|
||||
if err = ns.LastError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = objectTarget.WriteHeader(rawObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.Copy(objectTarget, options.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ids *transformer.AccessIdentifiers
|
||||
ids, err = objectTarget.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
address.SetObjectID(ids.SelfID())
|
||||
} else {
|
||||
rawObject := object.NewRaw()
|
||||
rawObject.SetContainerID(options.ContainerID)
|
||||
rawObject.SetOwnerID(options.OwnerID)
|
||||
ops := new(client.PutObjectParams).
|
||||
WithObject(rawObject.Object()).
|
||||
WithPayloadReader(options.Reader)
|
||||
objectID, err = options.Client.PutObject(
|
||||
ctx,
|
||||
ops,
|
||||
client.WithSession(options.SessionToken),
|
||||
client.WithBearer(options.BearerToken),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
address.SetObjectID(objectID)
|
||||
}
|
||||
address.SetContainerID(options.ContainerID)
|
||||
return address, nil
|
||||
}
|
||||
|
||||
func (oc *objectClient) Get(ctx context.Context, options *GetOptions) (*object.Object, error) {
|
||||
var (
|
||||
err error
|
||||
obj *object.Object
|
||||
)
|
||||
ops := new(client.GetObjectParams).
|
||||
WithAddress(options.ObjectAddress).
|
||||
WithPayloadWriter(options.Writer)
|
||||
obj, err = options.Client.GetObject(
|
||||
ctx,
|
||||
ops,
|
||||
client.WithSession(options.SessionToken),
|
||||
client.WithBearer(options.BearerToken),
|
||||
)
|
||||
return obj, err
|
||||
}
|
||||
|
||||
func (oc *objectClient) Search(ctx context.Context, options *SearchOptions) ([]*object.ID, error) {
|
||||
sfs := object.NewSearchFilters()
|
||||
sfs.AddRootFilter()
|
||||
sfs.AddFilter(options.Attribute.Key, options.Attribute.Value, object.MatchStringEqual)
|
||||
sops := new(client.SearchObjectParams)
|
||||
sops.WithContainerID(options.ContainerID)
|
||||
sops.WithSearchFilters(sfs)
|
||||
return options.Client.SearchObject(
|
||||
ctx,
|
||||
sops,
|
||||
client.WithSession(options.SessionToken),
|
||||
client.WithBearer(options.BearerToken),
|
||||
)
|
||||
}
|
||||
|
||||
func (oc *objectClient) Delete(ctx context.Context, options *DeleteOptions) error {
|
||||
ops := new(client.DeleteObjectParams).WithAddress(options.ObjectAddress)
|
||||
err := options.Client.DeleteObject(
|
||||
ctx,
|
||||
ops,
|
||||
client.WithSession(options.SessionToken),
|
||||
client.WithBearer(options.BearerToken),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
type remoteClientTarget struct {
|
||||
ctx context.Context
|
||||
client client.Client
|
||||
object *object.Object
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (rct *remoteClientTarget) WriteHeader(raw *objectCore.RawObject) error {
|
||||
rct.object = raw.Object().SDK()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rct *remoteClientTarget) Write(p []byte) (n int, err error) {
|
||||
rct.payload = append(rct.payload, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (rct *remoteClientTarget) Close() (*transformer.AccessIdentifiers, error) {
|
||||
id, err := rct.client.PutObject(
|
||||
rct.ctx, new(client.PutObjectParams).
|
||||
WithObject(rct.object).
|
||||
WithPayloadReader(bytes.NewReader(rct.payload)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new(transformer.AccessIdentifiers).WithSelfID(id), nil
|
||||
}
|
||||
|
||||
type networkState struct {
|
||||
ctx context.Context
|
||||
client client.Client
|
||||
lastError error
|
||||
onError func(error)
|
||||
}
|
||||
|
||||
func newNetworkState(ctx context.Context, client client.Client) *networkState {
|
||||
ns := &networkState{
|
||||
ctx: ctx,
|
||||
client: client,
|
||||
}
|
||||
ns.onError = func(err error) { ns.lastError = err }
|
||||
return ns
|
||||
}
|
||||
|
||||
func (ns *networkState) LastError() error {
|
||||
return ns.lastError
|
||||
}
|
||||
|
||||
func (ns *networkState) CurrentEpoch() uint64 {
|
||||
ce, err := ns.client.NetworkInfo(ns.ctx)
|
||||
if err != nil {
|
||||
ns.onError(err)
|
||||
}
|
||||
return ce.CurrentEpoch()
|
||||
}
|
67
neofs/credentials.go
Normal file
67
neofs/credentials.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package neofs
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/owner"
|
||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||
)
|
||||
|
||||
type (
|
||||
// Credentials contains methods that needed to work with NeoFS.
|
||||
Credentials interface {
|
||||
WIF() string
|
||||
Owner() *owner.ID
|
||||
PublicKey() *ecdsa.PublicKey
|
||||
PrivateKey() *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
cred struct {
|
||||
key *ecdsa.PrivateKey
|
||||
owner *owner.ID
|
||||
wif string
|
||||
}
|
||||
)
|
||||
|
||||
// New creates an instance of Credentials through string representation of secret.
|
||||
// It allows passing WIF, path, hex-encoded and others.
|
||||
func NewCredentials(secret string) (Credentials, error) {
|
||||
key, err := crypto.LoadPrivateKey(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return setFromPrivateKey(key)
|
||||
}
|
||||
|
||||
// PrivateKey returns ecdsa.PrivateKey.
|
||||
func (c *cred) PrivateKey() *ecdsa.PrivateKey {
|
||||
return c.key
|
||||
}
|
||||
|
||||
// PublicKey returns ecdsa.PublicKey.
|
||||
func (c *cred) PublicKey() *ecdsa.PublicKey {
|
||||
return &c.key.PublicKey
|
||||
}
|
||||
|
||||
// Owner returns owner.ID.
|
||||
func (c *cred) Owner() *owner.ID {
|
||||
return c.owner
|
||||
}
|
||||
|
||||
// WIF returns string representation of WIF.
|
||||
func (c *cred) WIF() string {
|
||||
return c.wif
|
||||
}
|
||||
|
||||
func setFromPrivateKey(key *ecdsa.PrivateKey) (*cred, error) {
|
||||
wallet, err := owner.NEO3WalletFromPublicKey(&key.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ownerID := owner.NewIDFromNeo3Wallet(wallet)
|
||||
wif, err := crypto.WIFEncode(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cred{key: key, owner: ownerID, wif: wif}, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue