[#232] Use contract to get container info
All checks were successful
/ DCO (pull_request) Successful in 32s
/ Vulncheck (pull_request) Successful in 47s
/ Builds (pull_request) Successful in 59s
/ OCI image (pull_request) Successful in 1m44s
/ Lint (pull_request) Successful in 2m11s
/ Tests (pull_request) Successful in 1m36s
/ Integration tests (pull_request) Successful in 6m12s
All checks were successful
/ DCO (pull_request) Successful in 32s
/ Vulncheck (pull_request) Successful in 47s
/ Builds (pull_request) Successful in 59s
/ OCI image (pull_request) Successful in 1m44s
/ Lint (pull_request) Successful in 2m11s
/ Tests (pull_request) Successful in 1m36s
/ Integration tests (pull_request) Successful in 6m12s
Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
This commit is contained in:
parent
ee628617a3
commit
34e986ba91
13 changed files with 274 additions and 90 deletions
|
@ -22,6 +22,8 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/middleware"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
|
||||
internalnet "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/net"
|
||||
containerClient "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/service/contracts/container"
|
||||
contractsUtil "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/service/contracts/util"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/service/frostfs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/templates"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/metrics"
|
||||
|
@ -39,6 +41,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
|
@ -275,6 +278,14 @@ func (a *app) initContainers(ctx context.Context) {
|
|||
a.corsCnrID = *corsCnrID
|
||||
}
|
||||
|
||||
func (a *app) initRPCClient(ctx context.Context) *rpcclient.Client {
|
||||
rpcCli, err := rpcclient.New(ctx, a.config().GetString(cfgRPCEndpoint), rpcclient.Options{})
|
||||
if err != nil {
|
||||
a.log.Fatal(logs.InitRPCClientFailed, zap.Error(err), logs.TagField(logs.TagApp))
|
||||
}
|
||||
return rpcCli
|
||||
}
|
||||
|
||||
func (a *app) initAppSettings(lc *logLevelConfig) {
|
||||
a.settings = &appSettings{
|
||||
reconnectInterval: fetchReconnectInterval(a.config()),
|
||||
|
@ -408,7 +419,7 @@ func (s *appSettings) FormContainerZone(ns string) string {
|
|||
s.mu.RLock()
|
||||
namespaces := s.defaultNamespaces
|
||||
s.mu.RUnlock()
|
||||
if slices.Contains(namespaces, ns) {
|
||||
if len(ns) == 0 || slices.Contains(namespaces, ns) {
|
||||
return v2container.SysAttributeZoneDefault
|
||||
}
|
||||
|
||||
|
@ -741,7 +752,22 @@ func (a *app) stopServices() {
|
|||
}
|
||||
|
||||
func (a *app) configureRouter(workerPool *ants.Pool) {
|
||||
a.handle = handler.New(a.AppParams(), a.settings, tree.NewTree(frostfs.NewPoolWrapper(a.treePool), a.log), workerPool)
|
||||
rpcCli := a.initRPCClient(a.ctx)
|
||||
cnrContractName := a.config().GetString(cfgContractsContainerName)
|
||||
rpcEndpoint := a.config().GetString(cfgRPCEndpoint)
|
||||
cnrAddr, err := contractsUtil.ResolveContractHash(cnrContractName, rpcEndpoint)
|
||||
if err != nil {
|
||||
a.log.Fatal(logs.FailedToResolveContractHash, zap.Error(err), logs.TagField(logs.TagApp))
|
||||
}
|
||||
cnrClient, err := containerClient.New(containerClient.Config{
|
||||
ContractHash: cnrAddr,
|
||||
Key: a.key,
|
||||
RPCClient: rpcCli,
|
||||
})
|
||||
if err != nil {
|
||||
a.log.Fatal(logs.InitContainerContractFailed, zap.Error(err), logs.TagField(logs.TagApp))
|
||||
}
|
||||
a.handle = handler.New(a.AppParams(), a.settings, tree.NewTree(frostfs.NewPoolWrapper(a.treePool), a.log), cnrClient, workerPool)
|
||||
|
||||
r := router.New()
|
||||
r.RedirectTrailingSlash = true
|
||||
|
|
|
@ -62,6 +62,8 @@ const (
|
|||
|
||||
defaultMultinetFallbackDelay = 300 * time.Millisecond
|
||||
|
||||
defaultContainerContractName = "container.frostfs"
|
||||
|
||||
cfgServer = "server"
|
||||
cfgTLSEnabled = "tls.enabled"
|
||||
cfgTLSCertFile = "tls.cert_file"
|
||||
|
@ -196,6 +198,9 @@ const (
|
|||
cmdConfig = "config"
|
||||
cmdConfigDir = "config-dir"
|
||||
cmdListenAddress = "listen_address"
|
||||
|
||||
// Contracts.
|
||||
cfgContractsContainerName = "contracts.container.name"
|
||||
)
|
||||
|
||||
var ignore = map[string]struct{}{
|
||||
|
@ -400,6 +405,9 @@ func setDefaults(v *viper.Viper, flags *pflag.FlagSet) {
|
|||
// multinet
|
||||
v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay)
|
||||
|
||||
// contracts
|
||||
v.SetDefault(cfgContractsContainerName, defaultContainerContractName)
|
||||
|
||||
if resolveMethods, err := flags.GetStringSlice(cfgResolveOrder); err == nil {
|
||||
v.SetDefault(cfgResolveOrder, resolveMethods)
|
||||
}
|
||||
|
|
|
@ -179,3 +179,6 @@ HTTP_GW_FEATURES_TREE_POOL_NETMAP_SUPPORT=true
|
|||
|
||||
# Containers properties
|
||||
HTTP_GW_CONTAINERS_CORS=AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||
|
||||
# Container contract hash (LE) or name in NNS.
|
||||
HTTP_GW_CONTRACTS_CONTAINER_NAME=container.frostfs
|
||||
|
|
|
@ -197,3 +197,8 @@ features:
|
|||
|
||||
containers:
|
||||
cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||
|
||||
contracts:
|
||||
container:
|
||||
# Container contract hash (LE) or name in NNS.
|
||||
name: container.frostfs
|
||||
|
|
|
@ -61,6 +61,7 @@ $ cat http.log
|
|||
| `multinet` | [Multinet configuration](#multinet-section) |
|
||||
| `features` | [Features configuration](#features-section) |
|
||||
| `containers` | [Containers configuration](#containers-section) |
|
||||
| `contracts` | [Contracts configuration](#contracts-section) |
|
||||
|
||||
# General section
|
||||
|
||||
|
@ -532,3 +533,15 @@ containers:
|
|||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||
|-------------|----------|---------------|---------------|-----------------------------------------|
|
||||
| `cors` | `string` | no | | Container name for CORS configurations. |
|
||||
|
||||
# `contracts` section
|
||||
|
||||
```yaml
|
||||
contracts:
|
||||
container:
|
||||
name: container.frostfs
|
||||
```
|
||||
|
||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||
|------------------|----------|---------------|---------------------|----------------------------------------------|
|
||||
| `container.name` | `string` | no | `container.frostfs` | Container contract hash (LE) or name in NNS. |
|
||||
|
|
2
go.mod
2
go.mod
|
@ -3,6 +3,7 @@ module git.frostfs.info/TrueCloudLab/frostfs-http-gw
|
|||
go 1.23
|
||||
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e
|
||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241125133852-37bd75821121
|
||||
git.frostfs.info/TrueCloudLab/frostfs-qos v0.0.0-20250128150313-cfbca7fa1dfe
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250317082814-87bb55f992dc
|
||||
|
@ -33,7 +34,6 @@ require (
|
|||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e // indirect
|
||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 // indirect
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1 // indirect
|
||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect
|
||||
|
|
42
internal/handler/container.go
Normal file
42
internal/handler/container.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (h *Handler) containerInfo(ctx context.Context, cnrID cid.ID) (*data.BucketInfo, error) {
|
||||
info := &data.BucketInfo{
|
||||
CID: cnrID,
|
||||
Name: cnrID.EncodeToString(),
|
||||
}
|
||||
res, err := h.cnrContract.GetContainerByID(cnrID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get frostfs container: %w", err)
|
||||
}
|
||||
|
||||
cnr := *res
|
||||
|
||||
if domain := container.ReadDomain(cnr); domain.Name() != "" {
|
||||
info.Name = domain.Name()
|
||||
info.Zone = domain.Zone()
|
||||
}
|
||||
info.HomomorphicHashDisabled = container.IsHomomorphicHashingDisabled(cnr)
|
||||
info.PlacementPolicy = cnr.PlacementPolicy()
|
||||
|
||||
if err = h.cache.Put(info); err != nil {
|
||||
h.reqLogger(ctx).Warn(logs.CouldntPutBucketIntoCache,
|
||||
zap.String("bucket name", info.Name),
|
||||
zap.Stringer("cid", info.CID),
|
||||
zap.Error(err),
|
||||
logs.TagField(logs.TagDatapath))
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
|
@ -233,6 +233,16 @@ func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) (Res
|
|||
return &resObjectSearchMock{res: res}, nil
|
||||
}
|
||||
|
||||
func (t *TestFrostFS) GetContainerByID(cid cid.ID) (*container.Container, error) {
|
||||
for k, v := range t.containers {
|
||||
if k == cid.EncodeToString() {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("container does not exist %s", cid)
|
||||
}
|
||||
|
||||
func (t *TestFrostFS) InitMultiObjectReader(context.Context, PrmInitMultiObjectReader) (io.Reader, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -166,12 +166,18 @@ type ContainerResolver interface {
|
|||
Resolve(ctx context.Context, zone, name string) (*cid.ID, error)
|
||||
}
|
||||
|
||||
type ContainerContract interface {
|
||||
// GetContainerByID reads a container from contract by ID.
|
||||
GetContainerByID(cid.ID) (*container.Container, error)
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
log *zap.Logger
|
||||
frostfs FrostFS
|
||||
ownerID *user.ID
|
||||
config Config
|
||||
containerResolver ContainerResolver
|
||||
cnrContract ContainerContract
|
||||
tree layer.TreeService
|
||||
cache *cache.BucketCache
|
||||
workerPool *ants.Pool
|
||||
|
@ -189,7 +195,7 @@ type AppParams struct {
|
|||
CORSCache *cache.CORSCache
|
||||
}
|
||||
|
||||
func New(params *AppParams, config Config, tree layer.TreeService, workerPool *ants.Pool) *Handler {
|
||||
func New(params *AppParams, config Config, tree layer.TreeService, rpcCli ContainerContract, workerPool *ants.Pool) *Handler {
|
||||
return &Handler{
|
||||
log: params.Logger,
|
||||
frostfs: params.FrostFS,
|
||||
|
@ -201,6 +207,7 @@ func New(params *AppParams, config Config, tree layer.TreeService, workerPool *a
|
|||
workerPool: workerPool,
|
||||
corsCnrID: params.CORSCnrID,
|
||||
corsCache: params.CORSCache,
|
||||
cnrContract: rpcCli,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,43 +386,7 @@ func (h *Handler) getBucketInfo(ctx context.Context, containerName string) (*dat
|
|||
return nil, fmt.Errorf("resolve container: %w", err)
|
||||
}
|
||||
|
||||
bktInfo, err := h.readContainer(ctx, *cnrID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read container: %w", err)
|
||||
}
|
||||
|
||||
if err = h.cache.Put(bktInfo); err != nil {
|
||||
h.reqLogger(ctx).Warn(logs.CouldntPutBucketIntoCache,
|
||||
zap.String("bucket name", bktInfo.Name),
|
||||
zap.Stringer("bucket cid", bktInfo.CID),
|
||||
zap.Error(err),
|
||||
logs.TagField(logs.TagDatapath))
|
||||
}
|
||||
|
||||
return bktInfo, nil
|
||||
}
|
||||
|
||||
func (h *Handler) readContainer(ctx context.Context, cnrID cid.ID) (*data.BucketInfo, error) {
|
||||
prm := PrmContainer{ContainerID: cnrID}
|
||||
res, err := h.frostfs.Container(ctx, prm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get frostfs container '%s': %w", cnrID.String(), err)
|
||||
}
|
||||
|
||||
bktInfo := &data.BucketInfo{
|
||||
CID: cnrID,
|
||||
Name: cnrID.EncodeToString(),
|
||||
}
|
||||
|
||||
if domain := container.ReadDomain(*res); domain.Name() != "" {
|
||||
bktInfo.Name = domain.Name()
|
||||
bktInfo.Zone = domain.Zone()
|
||||
}
|
||||
|
||||
bktInfo.HomomorphicHashDisabled = container.IsHomomorphicHashingDisabled(*res)
|
||||
bktInfo.PlacementPolicy = res.PlacementPolicy()
|
||||
|
||||
return bktInfo, err
|
||||
return h.containerInfo(ctx, *cnrID)
|
||||
}
|
||||
|
||||
func (h *Handler) browseIndex(ctx context.Context, req *fasthttp.RequestCtx, cidParam, oidParam string, isNativeList bool) {
|
||||
|
|
|
@ -174,7 +174,7 @@ func prepareHandlerContextBase(logger *zap.Logger) (*handlerContext, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handler := New(params, cfgMock, treeMock, workerPool)
|
||||
handler := New(params, cfgMock, treeMock, testFrostFS, workerPool)
|
||||
|
||||
return &handlerContext{
|
||||
key: key,
|
||||
|
|
|
@ -73,6 +73,9 @@ const (
|
|||
FailedToReadIndexPageTemplate = "failed to read index page template"
|
||||
SetCustomIndexPageTemplate = "set custom index page template"
|
||||
CouldNotFetchCORSContainerInfo = "couldn't fetch CORS container info"
|
||||
InitRPCClientFailed = "init rpc client faileds"
|
||||
InitContainerContractFailed = "init container contract failed"
|
||||
FailedToResolveContractHash = "failed to resolve contract hash"
|
||||
)
|
||||
|
||||
// Log messages with the "datapath" tag.
|
||||
|
@ -125,6 +128,7 @@ const (
|
|||
EmptyAccessControlRequestMethodHeader = "empty Access-Control-Request-Method request header"
|
||||
CORSRuleWasNotMatched = "cors rule was not matched"
|
||||
CouldntCacheCors = "couldn't cache cors"
|
||||
CouldNotParseContainerObjectLockEnabledAttribute = "could not parse container object lock enabled attribute"
|
||||
)
|
||||
|
||||
// Log messages with the "external_storage" tag.
|
||||
|
|
68
internal/service/contracts/container/client.go
Normal file
68
internal/service/contracts/container/client.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
containerContract "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
contract *containerContract.Contract
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
ContractHash util.Uint160
|
||||
Key *keys.PrivateKey
|
||||
RPCClient *rpcclient.Client
|
||||
}
|
||||
|
||||
func New(cfg Config) (*Client, error) {
|
||||
var err error
|
||||
key := cfg.Key
|
||||
if key == nil {
|
||||
if key, err = keys.NewPrivateKey(); err != nil {
|
||||
return nil, fmt.Errorf("generate anon private key for container contract: %w", err)
|
||||
}
|
||||
}
|
||||
acc := wallet.NewAccountFromPrivateKey(key)
|
||||
|
||||
act, err := actor.NewSimple(cfg.RPCClient, acc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create new actor: %w", err)
|
||||
}
|
||||
|
||||
return &Client{
|
||||
contract: containerContract.New(act, cfg.ContractHash),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetContainerByID(cnrID cid.ID) (*container.Container, error) {
|
||||
items, err := c.contract.Get(cnrID[:])
|
||||
if err != nil {
|
||||
return nil, handler.ErrContainerNotFound
|
||||
}
|
||||
|
||||
if len(items) != 4 {
|
||||
return nil, fmt.Errorf("unexpected container stack item count: %d", len(items))
|
||||
}
|
||||
|
||||
cnrBytes, err := items[0].TryBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get byte array of container: %w", err)
|
||||
}
|
||||
|
||||
var cnr container.Container
|
||||
if err = cnr.Unmarshal(cnrBytes); err != nil {
|
||||
return nil, fmt.Errorf("can't unmarshal container: %w", err)
|
||||
}
|
||||
|
||||
return &cnr, nil
|
||||
}
|
34
internal/service/contracts/util/util.go
Normal file
34
internal/service/contracts/util/util.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// ResolveContractHash determine contract hash by resolving NNS name.
|
||||
func ResolveContractHash(contractHash, rpcAddress string) (util.Uint160, error) {
|
||||
if hash, err := util.Uint160DecodeStringLE(contractHash); err == nil {
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
splitName := strings.Split(contractHash, ".")
|
||||
if len(splitName) != 2 {
|
||||
return util.Uint160{}, fmt.Errorf("invalid contract name: '%s'", contractHash)
|
||||
}
|
||||
|
||||
var domain container.Domain
|
||||
domain.SetName(splitName[0])
|
||||
domain.SetZone(splitName[1])
|
||||
|
||||
var nns ns.NNS
|
||||
if err := nns.Dial(rpcAddress); err != nil {
|
||||
return util.Uint160{}, fmt.Errorf("dial nns %s: %w", rpcAddress, err)
|
||||
}
|
||||
defer nns.Close()
|
||||
|
||||
return nns.ResolveContractHash(domain)
|
||||
}
|
Loading…
Add table
Reference in a new issue