diff --git a/cmd/frostfs-node/billing.go b/cmd/frostfs-node/billing.go new file mode 100644 index 000000000..e09999257 --- /dev/null +++ b/cmd/frostfs-node/billing.go @@ -0,0 +1,61 @@ +package main + +import ( + "context" + "net" + + billingconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/billing" + "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/billing" + billingSvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/billing/server" + "go.uber.org/zap" + "google.golang.org/grpc" +) + +const serviceNameBilling = "billing" + +func initBillingService(c *cfg) { + endpoint := billingconfig.GRPC(c.appCfg).Endpoint() + if endpoint == billingconfig.GRPCEndpointDefault { + c.log.Info(logs.FrostFSNodeBillingServiceDisabled) + return + } + + pubs := billingconfig.AuthorizedKeys(c.appCfg) + rawPubs := make([][]byte, 0, len(pubs)+1) + rawPubs = append(rawPubs, c.key.PublicKey().Bytes()) + + for i := range pubs { + rawPubs = append(rawPubs, pubs[i].Bytes()) + } + + billingSvc := billingSvc.New( + &c.key.PrivateKey, + rawPubs, + c.cfgObject.cnrSource, + c.cfgObject.cfgLocalStorage.localStorage, + ) + + lis, err := net.Listen("tcp", endpoint) + if err != nil { + c.log.Error(logs.FrostFSNodeCantListenGRPCEndpointBilling, zap.Error(err)) + return + } + + c.cfgBillingService.server = grpc.NewServer() + + c.onShutdown(func() { + stopGRPC("FrostFS Billing API", c.cfgBillingService.server, c.log) + }) + + billing.RegisterBillingServiceServer(c.cfgBillingService.server, billingSvc) + + c.workers = append(c.workers, newWorkerFromFunc(func(ctx context.Context) { + runAndLog(ctx, c, serviceNameBilling, false, func(context.Context, *cfg) { + c.log.Info(logs.FrostFSNodeStartListeningEndpoint, + zap.String("service", serviceNameBilling), + zap.String("endpoint", endpoint)) + fatalOnErr(c.cfgBillingService.server.Serve(lis)) + }) + })) +} diff --git a/cmd/frostfs-node/config.go b/cmd/frostfs-node/config.go index 2b185cfc8..f8dc0d4ab 100644 --- a/cmd/frostfs-node/config.go +++ b/cmd/frostfs-node/config.go @@ -476,6 +476,7 @@ type cfg struct { cfgNetmap cfgNetmap cfgControlService cfgControlService cfgObject cfgObject + cfgBillingService cfgBillingService } // ReadCurrentNetMap reads network map which has been cached at the @@ -656,6 +657,10 @@ type cfgControlService struct { server *grpc.Server } +type cfgBillingService struct { + server *grpc.Server +} + var persistateSideChainLastBlockKey = []byte("side_chain_last_processed_block") func initCfg(appCfg *config.Config) *cfg { diff --git a/cmd/frostfs-node/config/billing/config.go b/cmd/frostfs-node/config/billing/config.go new file mode 100644 index 000000000..15d4149d0 --- /dev/null +++ b/cmd/frostfs-node/config/billing/config.go @@ -0,0 +1,58 @@ +package billing + +import ( + "fmt" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" +) + +type GRPCConfig struct { + cfg *config.Config +} + +const ( + subsection = "billing" + grpcSubsection = "grpc" + + // GRPCEndpointDefault is a default endpoint of gRPC Billing service. + GRPCEndpointDefault = "" +) + +// AuthorizedKeys parses and returns an array of "authorized_keys" config +// parameter from "control" section. +// +// Returns an empty list if not set. +func AuthorizedKeys(c *config.Config) keys.PublicKeys { + strKeys := config.StringSliceSafe(c.Sub(subsection), "authorized_keys") + pubs := make(keys.PublicKeys, 0, len(strKeys)) + + for i := range strKeys { + pub, err := keys.NewPublicKeyFromString(strKeys[i]) + if err != nil { + panic(fmt.Errorf("invalid permitted key for Billing service %s: %w", strKeys[i], err)) + } + + pubs = append(pubs, pub) + } + + return pubs +} + +func GRPC(c *config.Config) GRPCConfig { + return GRPCConfig{ + c.Sub(subsection).Sub(grpcSubsection), + } +} + +// Endpoint returns the value of "endpoint" config parameter. +// +// Returns GRPCEndpointDefault if the value is not a non-empty string. +func (g GRPCConfig) Endpoint() string { + v := config.String(g.cfg, "endpoint") + if v != "" { + return v + } + + return GRPCEndpointDefault +} diff --git a/cmd/frostfs-node/config/billing/config_test.go b/cmd/frostfs-node/config/billing/config_test.go new file mode 100644 index 000000000..85251e9e5 --- /dev/null +++ b/cmd/frostfs-node/config/billing/config_test.go @@ -0,0 +1,37 @@ +package billing_test + +import ( + "testing" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" + billingconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/billing" + configtest "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/test" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/stretchr/testify/require" +) + +func TestBillingSection(t *testing.T) { + t.Run("defaults", func(t *testing.T) { + empty := configtest.EmptyConfig() + + require.Empty(t, billingconfig.AuthorizedKeys(empty)) + require.Equal(t, billingconfig.GRPCEndpointDefault, billingconfig.GRPC(empty).Endpoint()) + }) + + const path = "../../../../config/example/node" + + pubs := make(keys.PublicKeys, 2) + pubs[0], _ = keys.NewPublicKeyFromString("035839e45d472a3b7769a2a1bd7d54c4ccd4943c3b40f547870e83a8fcbfb3ce11") + pubs[1], _ = keys.NewPublicKeyFromString("028f42cfcb74499d7b15b35d9bff260a1c8d27de4f446a627406a382d8961486d6") + + fileConfigTest := func(c *config.Config) { + require.Equal(t, pubs, billingconfig.AuthorizedKeys(c)) + require.Equal(t, "localhost:8092", billingconfig.GRPC(c).Endpoint()) + } + + configtest.ForEachFileType(path, fileConfigTest) + + t.Run("ENV", func(t *testing.T) { + configtest.ForEnvFileType(t, path, fileConfigTest) + }) +} diff --git a/cmd/frostfs-node/main.go b/cmd/frostfs-node/main.go index e4f0a434c..3eb7fcd95 100644 --- a/cmd/frostfs-node/main.go +++ b/cmd/frostfs-node/main.go @@ -115,6 +115,7 @@ func initApp(ctx context.Context, c *cfg) { initAndLog(c, "tree", initTreeService) initAndLog(c, "apemanager", initAPEManagerService) initAndLog(c, "control", initControlService) + initAndLog(c, "billing", initBillingService) initAndLog(c, "morph notifications", func(c *cfg) { listenMorphNotifications(ctx, c) }) } diff --git a/config/example/node.env b/config/example/node.env index 72f56e96c..60cdfcf8f 100644 --- a/config/example/node.env +++ b/config/example/node.env @@ -49,6 +49,10 @@ FROSTFS_GRPC_1_TLS_ENABLED=false FROSTFS_CONTROL_AUTHORIZED_KEYS="035839e45d472a3b7769a2a1bd7d54c4ccd4943c3b40f547870e83a8fcbfb3ce11 028f42cfcb74499d7b15b35d9bff260a1c8d27de4f446a627406a382d8961486d6" FROSTFS_CONTROL_GRPC_ENDPOINT=localhost:8090 +# Billing service section +FROSTFS_BILLING_AUTHORIZED_KEYS="035839e45d472a3b7769a2a1bd7d54c4ccd4943c3b40f547870e83a8fcbfb3ce11 028f42cfcb74499d7b15b35d9bff260a1c8d27de4f446a627406a382d8961486d6" +FROSTFS_BILLING_GRPC_ENDPOINT=localhost:8092 + # Contracts section FROSTFS_CONTRACTS_BALANCE=5263abba1abedbf79bb57f3e40b50b4425d2d6cd FROSTFS_CONTRACTS_CONTAINER=5d084790d7aa36cea7b53fe897380dab11d2cd3c diff --git a/config/example/node.json b/config/example/node.json index b9dc6014c..1ef5979b4 100644 --- a/config/example/node.json +++ b/config/example/node.json @@ -82,6 +82,15 @@ "endpoint": "localhost:8090" } }, + "billing": { + "authorized_keys": [ + "035839e45d472a3b7769a2a1bd7d54c4ccd4943c3b40f547870e83a8fcbfb3ce11", + "028f42cfcb74499d7b15b35d9bff260a1c8d27de4f446a627406a382d8961486d6" + ], + "grpc": { + "endpoint": "localhost:8092" + } + }, "contracts": { "balance": "5263abba1abedbf79bb57f3e40b50b4425d2d6cd", "container": "5d084790d7aa36cea7b53fe897380dab11d2cd3c", diff --git a/config/example/node.yaml b/config/example/node.yaml index bad67816a..df5fb1c7e 100644 --- a/config/example/node.yaml +++ b/config/example/node.yaml @@ -69,6 +69,13 @@ control: grpc: endpoint: localhost:8090 # endpoint that is listened by the Control Service +billing: + authorized_keys: # list of hex-encoded public keys that have rights to use the Billing Service + - 035839e45d472a3b7769a2a1bd7d54c4ccd4943c3b40f547870e83a8fcbfb3ce11 + - 028f42cfcb74499d7b15b35d9bff260a1c8d27de4f446a627406a382d8961486d6 + grpc: + endpoint: localhost:8092 # endpoint that is listened by the Billing Service + contracts: # side chain NEOFS contract script hashes; optional, override values retrieved from NNS contract balance: 5263abba1abedbf79bb57f3e40b50b4425d2d6cd container: 5d084790d7aa36cea7b53fe897380dab11d2cd3c diff --git a/dev/.vscode-example/launch.json b/dev/.vscode-example/launch.json index 990fd42a8..e09a952ba 100644 --- a/dev/.vscode-example/launch.json +++ b/dev/.vscode-example/launch.json @@ -49,6 +49,8 @@ "FROSTFS_GRPC_0_ENDPOINT":"127.0.0.1:8080", "FROSTFS_CONTROL_GRPC_ENDPOINT":"127.0.0.1:8081", "FROSTFS_CONTROL_AUTHORIZED_KEYS":"031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a", + "FROSTFS_BILLING_GRPC_ENDPOINT":"127.0.0.1:8082", + "FROSTFS_BILLING_AUTHORIZED_KEYS":"031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a", "FROSTFS_NODE_ATTRIBUTE_0":"User-Agent:FrostFS/dev", "FROSTFS_NODE_ATTRIBUTE_1":"UN-LOCODE:RU MOW", "FROSTFS_NODE_PERSISTENT_STATE_PATH":"${workspaceFolder}/.cache/state/.frostfs-node-s1-state", diff --git a/docs/storage-node-configuration.md b/docs/storage-node-configuration.md index 5389bfbb5..0db423b33 100644 --- a/docs/storage-node-configuration.md +++ b/docs/storage-node-configuration.md @@ -18,6 +18,7 @@ There are some custom types used for brevity: | `pprof` | [PProf configuration](#pprof-section) | | `prometheus` | [Prometheus metrics configuration](#prometheus-section) | | `control` | [Control service configuration](#control-section) | +| `billing` | [Billing service configuration](#billing-section) | | `contracts` | [Override FrostFS contracts hashes](#contracts-section) | | `morph` | [N3 blockchain client configuration](#morph-section) | | `apiclient` | [FrostFS API client configuration](#apiclient-section) | @@ -42,6 +43,20 @@ control: | `authorized_keys` | `[]public key` | empty | List of public keys which are used to authorize requests to the control service. | | `grpc.endpoint` | `string` | empty | Address that control service listener binds to. | +# `billing` section +```yaml +billing: + authorized_keys: + - 035839e45d472a3b7769a2a1bd7d54c4ccd4943c3b40f547870e83a8fcbfb3ce11 + - 028f42cfcb74499d7b15b35d9bff260a1c8d27de4f446a627406a382d8961486d6 + grpc: + endpoint: 127.0.0.1:8092 +``` +| Parameter | Type | Default value | Description | +|-------------------|----------------|---------------|----------------------------------------------------------------------------------| +| `authorized_keys` | `[]public key` | empty | List of public keys which are used to authorize requests to the billing service. | +| `grpc.endpoint` | `string` | empty | Address that billing service listener binds to. | + # `grpc` section ```yaml grpc: diff --git a/internal/logs/logs.go b/internal/logs/logs.go index 78f00c4ee..e53432e8d 100644 --- a/internal/logs/logs.go +++ b/internal/logs/logs.go @@ -443,6 +443,8 @@ const ( FrostFSNodeRemovingAllTreesForContainer = "removing all trees for container" FrostFSNodeContainerRemovalEventReceivedButTreesWerentRemoved = "container removal event received, but trees weren't removed" FrostFSNodeCantListenGRPCEndpointControl = "can't listen gRPC endpoint (control)" + FrostFSNodeCantListenGRPCEndpointBilling = "can't listen gRPC endpoint (billing)" + FrostFSNodeBillingServiceDisabled = "billing service is disabled" FrostFSNodePolicerIsDisabled = "policer is disabled" CommonApplicationStarted = "application started" ShardGCCollectingExpiredObjectsStarted = "collecting expired objects started" diff --git a/pkg/local_object_storage/engine/container_stat.go b/pkg/local_object_storage/engine/container_stat.go new file mode 100644 index 000000000..f1e419e09 --- /dev/null +++ b/pkg/local_object_storage/engine/container_stat.go @@ -0,0 +1,167 @@ +package engine + +import ( + "bytes" + "context" + "errors" + "sort" + "sync" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard" + "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "golang.org/x/sync/errgroup" +) + +type ContainerStatPrm struct { + ContainerID []cid.ID + + Limit uint32 + StartFromContainerID *cid.ID +} + +type ContainerStatRes struct { + ContainerStats []ContainerStat + Partial bool +} + +type ContainerStat struct { + ContainerID cid.ID + SizeLogic uint64 + CountPhy, CountLogic, CountUser uint64 +} + +var errInvalidLimit = errors.New("limit must be greater than zero") + +func (e *StorageEngine) ContainerStat(ctx context.Context, prm ContainerStatPrm) (*ContainerStatRes, error) { + if e.metrics != nil { + defer elapsed("ContainerStat", e.metrics.AddMethodDuration)() + } + + ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.ContainerStat", + trace.WithAttributes( + attribute.Int("container_ids", len(prm.ContainerID)), + attribute.Int64("limit", int64(prm.Limit)), + attribute.Bool("start_from_container_id", prm.StartFromContainerID != nil), + )) + defer span.End() + + if len(prm.ContainerID) == 0 && prm.Limit == 0 { + return nil, errInvalidLimit + } + + var result *ContainerStatRes + err := e.execIfNotBlocked(func() error { + var sErr error + result, sErr = e.containerStat(ctx, prm) + return sErr + }) + return result, err +} + +func (e *StorageEngine) containerStat(ctx context.Context, prm ContainerStatPrm) (*ContainerStatRes, error) { + e.mtx.RLock() + defer e.mtx.RUnlock() + + if len(prm.ContainerID) > 0 { + sort.Slice(prm.ContainerID, func(i, j int) bool { + return bytes.Compare(prm.ContainerID[i][:], prm.ContainerID[j][:]) < 0 + }) + } + + shardResults, partial, err := e.collectShardContainerStats(ctx, prm) + if err != nil { + return nil, err + } + + return &ContainerStatRes{ + ContainerStats: e.mergeShardContainerStats(shardResults, int(prm.Limit)), + Partial: partial, + }, nil +} + +func (e *StorageEngine) collectShardContainerStats(ctx context.Context, prm ContainerStatPrm) ([][]shard.ContainerStat, bool, error) { + if len(prm.ContainerID) > 0 { + sort.Slice(prm.ContainerID, func(i, j int) bool { + return bytes.Compare(prm.ContainerID[i][:], prm.ContainerID[j][:]) < 0 + }) + } + + var shardResults [][]shard.ContainerStat + var shardErrors []error + var resultsGuard sync.Mutex + + shPrm := shard.ContainerStatPrm{ + ContainerID: prm.ContainerID, + Limit: prm.Limit, + StartFromContainerID: prm.StartFromContainerID, + } + + eg, egCtx := errgroup.WithContext(ctx) + var shardsCount int + e.iterateOverUnsortedShards(func(hs hashedShard) (stop bool) { + shardsCount++ + eg.Go(func() error { + s, err := hs.ContainerStat(egCtx, shPrm) + resultsGuard.Lock() + defer resultsGuard.Unlock() + + if err != nil { + shardErrors = append(shardErrors, err) + return nil + } + + if len(s) > 0 { + shardResults = append(shardResults, s) + } + + return nil + }) + return false + }) + if err := eg.Wait(); err != nil { + return nil, false, err + } + if shardsCount == len(shardErrors) { + return nil, false, errors.Join(shardErrors...) + } + + return shardResults, len(shardErrors) > 0, nil +} + +func (e *StorageEngine) mergeShardContainerStats(shardResults [][]shard.ContainerStat, limit int) []ContainerStat { + var stats []ContainerStat + for len(stats) <= limit && len(shardResults) > 0 { + // shard results are sorted by container ID + sort.Slice(shardResults, func(i, j int) bool { + return bytes.Compare(shardResults[i][0].ContainerID[:], shardResults[j][0].ContainerID[:]) < 0 + }) + + if len(stats) > 0 && stats[len(stats)-1].ContainerID == shardResults[0][0].ContainerID { + stats[len(stats)-1].SizeLogic += shardResults[0][0].SizeLogic + stats[len(stats)-1].CountPhy += shardResults[0][0].CountPhy + stats[len(stats)-1].CountLogic += shardResults[0][0].CountLogic + stats[len(stats)-1].CountUser += shardResults[0][0].CountUser + } else { + stats = append(stats, ContainerStat{ + ContainerID: shardResults[0][0].ContainerID, + SizeLogic: shardResults[0][0].SizeLogic, + CountPhy: shardResults[0][0].CountPhy, + CountLogic: shardResults[0][0].CountLogic, + CountUser: shardResults[0][0].CountUser, + }) + } + + if len(shardResults[0]) == 1 { // last item for shard + shardResults = shardResults[1:] + } else { + shardResults[0] = shardResults[0][1:] + } + } + if len(stats) > limit { + stats = stats[:limit] + } + return stats +} diff --git a/pkg/local_object_storage/engine/container_stat_test.go b/pkg/local_object_storage/engine/container_stat_test.go new file mode 100644 index 000000000..264f6d4b6 --- /dev/null +++ b/pkg/local_object_storage/engine/container_stat_test.go @@ -0,0 +1,221 @@ +package engine + +import ( + "context" + "testing" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/internal/testutil" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" + objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" + oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test" + "github.com/stretchr/testify/require" +) + +func TestContainerList(t *testing.T) { + t.Parallel() + + s1 := testNewShard(t) + s2 := testNewShard(t) + s3 := testNewShard(t) + + e := testNewEngine(t).setInitializedShards(t, s1, s2, s3).engine + e.log = test.NewLogger(t) + defer e.Close(context.Background()) + + const containerCount = 10 + expStat := testPutComplexObject(t, []*shard.Shard{s1, s2, s3}, containerCount, nil) + expStat = testPutSimpleObject(t, []*shard.Shard{s1, s2, s3}, containerCount, expStat) + expStat = testPutSimpleObject(t, []*shard.Shard{s1, s2}, containerCount, expStat) + expStat = testPutSimpleObject(t, []*shard.Shard{s2, s3}, containerCount, expStat) + expStat = testPutSimpleObject(t, []*shard.Shard{s1}, containerCount, expStat) + expStat = testPutSimpleObject(t, []*shard.Shard{s2}, containerCount, expStat) + expStat = testPutSimpleObject(t, []*shard.Shard{s3}, containerCount, expStat) + + t.Run("with default limit", func(t *testing.T) { + var prm ContainerStatPrm + prm.Limit = 10_000 + res, err := e.ContainerStat(context.Background(), prm) + require.NoError(t, err) + require.NotNil(t, res) + require.ElementsMatch(t, expStat, res.ContainerStats) + require.False(t, res.Partial) + }) + + t.Run("with limit, batched", func(t *testing.T) { + var prm ContainerStatPrm + prm.Limit = 1 + var stats []ContainerStat + + for { + res, err := e.ContainerStat(context.Background(), prm) + require.NoError(t, err) + require.NotNil(t, res) + require.False(t, res.Partial) + if len(res.ContainerStats) == 0 { + break + } + stats = append(stats, res.ContainerStats...) + last := res.ContainerStats[len(res.ContainerStats)-1].ContainerID + prm.StartFromContainerID = &last + prm.Limit += 1 + } + + require.ElementsMatch(t, expStat, stats) + }) + + t.Run("by container id", func(t *testing.T) { + for _, cc := range []int{1, 2, 3, 4, 5} { + var prm ContainerStatPrm + for idx := 0; idx+cc < len(expStat); idx += cc { + prm.ContainerID = nil + for i := 0; i < cc; i++ { + prm.ContainerID = append(prm.ContainerID, expStat[idx+i].ContainerID) + } + prm.Limit = uint32(len(prm.ContainerID)) + res, err := e.ContainerStat(context.Background(), prm) + require.NoError(t, err) + require.NotNil(t, res) + require.False(t, res.Partial) + require.ElementsMatch(t, expStat[idx:idx+cc], res.ContainerStats) + } + } + }) + + t.Run("unknown container id", func(t *testing.T) { + var prm ContainerStatPrm + prm.ContainerID = append(prm.ContainerID, cidtest.ID()) + prm.Limit = uint32(len(prm.ContainerID)) + res, err := e.ContainerStat(context.Background(), prm) + require.NoError(t, err) + require.NotNil(t, res) + require.False(t, res.Partial) + require.ElementsMatch(t, []ContainerStat{{ContainerID: prm.ContainerID[0]}}, res.ContainerStats) + }) + + t.Run("degraded shard", func(t *testing.T) { + s1.SetMode(mode.Degraded) + var prm ContainerStatPrm + prm.Limit = 10_000 + res, err := e.ContainerStat(context.Background(), prm) + require.NoError(t, err) + require.NotNil(t, res) + require.True(t, res.Partial) + }) +} + +func testPutComplexObject(t *testing.T, shards []*shard.Shard, count int, stats []ContainerStat) []ContainerStat { + const payloadSize = 10 * 1024 + for count > 0 { + var cnr cid.ID + var stat *ContainerStat + var newStat bool + if len(stats) == 0 || count%2 == 0 { + cnr = cidtest.ID() + stat = &ContainerStat{ContainerID: cnr} + newStat = true + } else { + cnr = stats[count%len(stats)].ContainerID + stat = &stats[count%len(stats)] + } + + parentID := oidtest.ID() + splitID := objectSDK.NewSplitID() + + parent := testutil.GenerateObjectWithCID(cnr) + parent.SetID(parentID) + parent.SetPayload(nil) + + const childCount = 10 + children := make([]*objectSDK.Object, childCount) + childIDs := make([]oid.ID, childCount) + for i := range children { + children[i] = testutil.GenerateObjectWithCID(cnr) + if i != 0 { + children[i].SetPreviousID(childIDs[i-1]) + } + if i == len(children)-1 { + children[i].SetParent(parent) + } + children[i].SetSplitID(splitID) + children[i].SetPayload(make([]byte, payloadSize)) + children[i].SetPayloadSize(payloadSize) + childIDs[i], _ = children[i].ID() + + stat.SizeLogic += payloadSize + stat.CountLogic += 1 + stat.CountPhy += 1 + } + stat.CountUser += 1 + + link := testutil.GenerateObjectWithCID(cnr) + link.SetParent(parent) + link.SetParentID(parentID) + link.SetSplitID(splitID) + link.SetChildren(childIDs...) + + stat.CountLogic += 1 + stat.CountPhy += 1 + stat.SizeLogic += link.PayloadSize() + + for i := range children { + sh := shards[i%len(shards)] + var putPrm shard.PutPrm + putPrm.SetObject(children[i]) + _, err := sh.Put(context.Background(), putPrm) + require.NoError(t, err) + } + sh := shards[count%len(shards)] + var putPrm shard.PutPrm + putPrm.SetObject(link) + _, err := sh.Put(context.Background(), putPrm) + require.NoError(t, err) + + if newStat { + stats = append(stats, *stat) + } + count-- + } + return stats +} + +func testPutSimpleObject(t *testing.T, shards []*shard.Shard, count int, stats []ContainerStat) []ContainerStat { + const payloadSize = 7 * 1024 + for count > 0 { + var cnr cid.ID + var stat *ContainerStat + var newStat bool + if len(stats) == 0 || count%2 == 0 { + cnr = cidtest.ID() + stat = &ContainerStat{ContainerID: cnr} + newStat = true + } else { + cnr = stats[count%len(stats)].ContainerID + stat = &stats[count%len(stats)] + } + + obj := testutil.GenerateObjectWithCID(cnr) + obj.SetPayload(make([]byte, payloadSize)) + obj.SetPayloadSize(payloadSize) + stat.SizeLogic += payloadSize + stat.CountLogic += 1 + stat.CountPhy += 1 + stat.CountUser += 1 + + sh := shards[count%len(shards)] + var putPrm shard.PutPrm + putPrm.SetObject(obj) + _, err := sh.Put(context.Background(), putPrm) + require.NoError(t, err) + + if newStat { + stats = append(stats, *stat) + } + count-- + } + return stats +} diff --git a/pkg/local_object_storage/metabase/container_stat.go b/pkg/local_object_storage/metabase/container_stat.go new file mode 100644 index 000000000..db4c1ee8d --- /dev/null +++ b/pkg/local_object_storage/metabase/container_stat.go @@ -0,0 +1,237 @@ +package meta + +import ( + "bytes" + "context" + "crypto/sha256" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/internal/metaerr" + "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "go.etcd.io/bbolt" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +type ContainerStatPrm struct { + ContainerID []cid.ID + + Limit uint32 + StartFromContainerID *cid.ID +} + +type ContainerStat struct { + ContainerID cid.ID + SizeLogic uint64 + CountPhy, CountLogic, CountUser uint64 +} + +// ContainerStat returns object count and size for containers. +// If len(prm.ContainerID) > 0, then result slice contains records in the same order as prm.ContainerID. +// Otherwise result slice sorted by ContainerID. +func (db *DB) ContainerStat(ctx context.Context, prm ContainerStatPrm) ([]ContainerStat, error) { + var ( + startedAt = time.Now() + success = false + ) + defer func() { + db.metrics.AddMethodDuration("ContainerStat", time.Since(startedAt), success) + }() + + _, span := tracing.StartSpanFromContext(ctx, "metabase.ContainerStat", + trace.WithAttributes( + attribute.Int("container_ids", len(prm.ContainerID)), + attribute.Int64("limit", int64(prm.Limit)), + attribute.Bool("start_from_container_id", prm.StartFromContainerID != nil), + )) + defer span.End() + + db.modeMtx.RLock() + defer db.modeMtx.RUnlock() + + if db.mode.NoMetabase() { + return nil, ErrDegradedMode + } + + if len(prm.ContainerID) > 0 { + return db.containerStatByContainerID(prm.ContainerID) + } + return db.containerStatByLimit(prm.StartFromContainerID, prm.Limit) +} + +func (db *DB) containerStatByContainerID(containerID []cid.ID) ([]ContainerStat, error) { + var result []ContainerStat + err := db.boltDB.View(func(tx *bbolt.Tx) error { + for _, contID := range containerID { + var stat ContainerStat + stat.ContainerID = contID + stat.SizeLogic = db.containerSize(tx, contID) + + counters, err := db.containerCounters(tx, contID) + if err != nil { + return err + } + stat.CountPhy = counters.Phy + stat.CountLogic = counters.Logic + stat.CountUser = counters.User + result = append(result, stat) + } + return nil + }) + if err != nil { + return nil, metaerr.Wrap(err) + } + return result, nil +} + +func (db *DB) containerStatByLimit(startFrom *cid.ID, limit uint32) ([]ContainerStat, error) { + var result []ContainerStat + + var lastKey []byte + if startFrom != nil { + lastKey = make([]byte, sha256.Size) + startFrom.Encode(lastKey) + } + + var counts []containerIDObjectCounters + var sizes []containerIDSize + + err := db.boltDB.View(func(tx *bbolt.Tx) error { + var e error + counts, e = getContainerCountersBatch(tx, lastKey, limit) + if e != nil { + return e + } + + sizes, e = getContainerSizesBatch(tx, lastKey, limit) + return e + }) + if err != nil { + return nil, metaerr.Wrap(err) + } + + result = mergeSizeAndCounts(counts, sizes) + if len(result) > int(limit) { + result = result[:limit] + } + return result, nil +} + +type containerIDObjectCounters struct { + ContainerID cid.ID + ObjectCounters +} + +func getContainerCountersBatch(tx *bbolt.Tx, lastKey []byte, limit uint32) ([]containerIDObjectCounters, error) { + var result []containerIDObjectCounters + b := tx.Bucket(containerCounterBucketName) + if b == nil { + return result, nil + } + c := b.Cursor() + var key, value []byte + for key, value = c.Seek(lastKey); key != nil && uint32(len(result)) < limit; key, value = c.Next() { + if bytes.Equal(lastKey, key) { + continue + } + + cnrID, err := parseContainerCounterKey(key) + if err != nil { + return nil, err + } + ent, err := parseContainerCounterValue(value) + if err != nil { + return nil, err + } + result = append(result, containerIDObjectCounters{ + ContainerID: cnrID, + ObjectCounters: ent, + }) + } + return result, nil +} + +type containerIDSize struct { + ContainerID cid.ID + Size uint64 +} + +func getContainerSizesBatch(tx *bbolt.Tx, lastKey []byte, limit uint32) ([]containerIDSize, error) { + var result []containerIDSize + b := tx.Bucket(containerVolumeBucketName) + c := b.Cursor() + var key, value []byte + for key, value = c.Seek(lastKey); key != nil && uint32(len(result)) < limit; key, value = c.Next() { + if bytes.Equal(lastKey, key) { + continue + } + + var r containerIDSize + r.Size = parseContainerSize(value) + if err := r.ContainerID.Decode(key); err != nil { + return nil, err + } + result = append(result, r) + } + return result, nil +} + +// mergeSizeAndCounts merges sizes and counts. +// As records are deleted in background, it can happen that metabase contains size record for container, +// but doesn't contain record for count. +func mergeSizeAndCounts(counts []containerIDObjectCounters, sizes []containerIDSize) []ContainerStat { + var result []ContainerStat + + for len(counts) > 0 || len(sizes) > 0 { + if len(counts) == 0 { + result = append(result, ContainerStat{ + ContainerID: sizes[0].ContainerID, + SizeLogic: sizes[0].Size, + }) + sizes = sizes[1:] + continue + } + + if len(sizes) == 0 { + result = append(result, ContainerStat{ + ContainerID: counts[0].ContainerID, + CountPhy: counts[0].Phy, + CountLogic: counts[0].Logic, + CountUser: counts[0].User, + }) + counts = counts[1:] + continue + } + + v := bytes.Compare(sizes[0].ContainerID[:], counts[0].ContainerID[:]) + + if v == 0 { // equal + result = append(result, ContainerStat{ + ContainerID: counts[0].ContainerID, + CountPhy: counts[0].Phy, + CountLogic: counts[0].Logic, + CountUser: counts[0].User, + SizeLogic: sizes[0].Size, + }) + counts = counts[1:] + sizes = sizes[1:] + } else if v < 0 { // from sizes + result = append(result, ContainerStat{ + ContainerID: sizes[0].ContainerID, + SizeLogic: sizes[0].Size, + }) + sizes = sizes[1:] + } else { // from counts + result = append(result, ContainerStat{ + ContainerID: counts[0].ContainerID, + CountPhy: counts[0].Phy, + CountLogic: counts[0].Logic, + CountUser: counts[0].User, + }) + counts = counts[1:] + } + } + + return result +} diff --git a/pkg/local_object_storage/metabase/containers.go b/pkg/local_object_storage/metabase/containers.go index 472b2affc..24a515486 100644 --- a/pkg/local_object_storage/metabase/containers.go +++ b/pkg/local_object_storage/metabase/containers.go @@ -65,20 +65,19 @@ func (db *DB) ContainerSize(id cid.ID) (size uint64, err error) { } err = db.boltDB.View(func(tx *bbolt.Tx) error { - size, err = db.containerSize(tx, id) - - return err + size = db.containerSize(tx, id) + return nil }) return size, metaerr.Wrap(err) } -func (db *DB) containerSize(tx *bbolt.Tx, id cid.ID) (uint64, error) { +func (db *DB) containerSize(tx *bbolt.Tx, id cid.ID) uint64 { containerVolume := tx.Bucket(containerVolumeBucketName) key := make([]byte, cidSize) id.Encode(key) - return parseContainerSize(containerVolume.Get(key)), nil + return parseContainerSize(containerVolume.Get(key)) } func parseContainerID(dst *cid.ID, name []byte, ignore map[string]struct{}) bool { diff --git a/pkg/local_object_storage/metabase/counter.go b/pkg/local_object_storage/metabase/counter.go index 275099ff2..d11710fd9 100644 --- a/pkg/local_object_storage/metabase/counter.go +++ b/pkg/local_object_storage/metabase/counter.go @@ -216,21 +216,28 @@ func (db *DB) ContainerCount(ctx context.Context, id cid.ID) (ObjectCounters, er var result ObjectCounters err := db.boltDB.View(func(tx *bbolt.Tx) error { - b := tx.Bucket(containerCounterBucketName) - key := make([]byte, cidSize) - id.Encode(key) - v := b.Get(key) - if v == nil { - return nil - } var err error - result, err = parseContainerCounterValue(v) + result, err = db.containerCounters(tx, id) return err }) return result, metaerr.Wrap(err) } +func (*DB) containerCounters(tx *bbolt.Tx, id cid.ID) (ObjectCounters, error) { + b := tx.Bucket(containerCounterBucketName) + if b == nil { + return ObjectCounters{}, nil + } + key := make([]byte, cidSize) + id.Encode(key) + v := b.Get(key) + if v == nil { + return ObjectCounters{}, nil + } + return parseContainerCounterValue(v) +} + func (db *DB) incCounters(tx *bbolt.Tx, cnrID cid.ID, isUserObject bool) error { b := tx.Bucket(shardInfoBucket) if b == nil { diff --git a/pkg/local_object_storage/shard/container_stat.go b/pkg/local_object_storage/shard/container_stat.go new file mode 100644 index 000000000..c213aa7fc --- /dev/null +++ b/pkg/local_object_storage/shard/container_stat.go @@ -0,0 +1,63 @@ +package shard + +import ( + "context" + + meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase" + "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +type ContainerStatPrm struct { + ContainerID []cid.ID + + Limit uint32 + StartFromContainerID *cid.ID +} + +type ContainerStat struct { + ContainerID cid.ID + SizeLogic uint64 + CountPhy, CountLogic, CountUser uint64 +} + +// ContainerStat returns object count and size for containers from metabase. +func (s *Shard) ContainerStat(ctx context.Context, prm ContainerStatPrm) ([]ContainerStat, error) { + _, span := tracing.StartSpanFromContext(ctx, "Shard.ContainerStat", + trace.WithAttributes( + attribute.String("shard_id", s.ID().String()), + attribute.Int("container_ids", len(prm.ContainerID)), + attribute.Int64("limit", int64(prm.Limit)), + attribute.Bool("start_from_container_id", prm.StartFromContainerID != nil), + )) + defer span.End() + + s.m.RLock() + defer s.m.RUnlock() + + if s.GetMode().NoMetabase() { + return nil, ErrDegradedMode + } + + res, err := s.metaBase.ContainerStat(ctx, meta.ContainerStatPrm{ + ContainerID: prm.ContainerID, + Limit: prm.Limit, + StartFromContainerID: prm.StartFromContainerID, + }) + if err != nil { + return nil, err + } + result := make([]ContainerStat, 0, len(res)) + for _, r := range res { + result = append(result, ContainerStat{ + ContainerID: r.ContainerID, + SizeLogic: r.SizeLogic, + CountPhy: r.CountPhy, + CountLogic: r.CountLogic, + CountUser: r.CountUser, + }) + } + return result, nil +} diff --git a/pkg/services/billing/server/list_containers.go b/pkg/services/billing/server/list_containers.go new file mode 100644 index 000000000..3882c879f --- /dev/null +++ b/pkg/services/billing/server/list_containers.go @@ -0,0 +1,137 @@ +package server + +import ( + "context" + "errors" + "fmt" + + containerApi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" + containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/billing" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + defaultLimit = 1000 + maxLimit = 10000 +) + +var ( + errInvalidContainerIDLenght = errors.New("count of container ID array must be lower or equal 10 000 items") + errInvalidLimit = errors.New("limit value must be lower or equal 10 000") +) + +func (s *Server) ListContainers(ctx context.Context, req *billing.ListContainersRequest) (*billing.ListContainersResponse, error) { + err := s.isValidRequest(req) + if err != nil { + return nil, status.Error(codes.PermissionDenied, err.Error()) + } + enginePrm, err := convertToEngineContainerStatPrm(req) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + res, err := s.se.ContainerStat(ctx, enginePrm) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + resp := &billing.ListContainersResponse{ + Body: &billing.ListContainersResponse_Body{ + Result: s.addContainerInfo(res.ContainerStats), + NextPageToken: containerListNextPageToken(res.ContainerStats, enginePrm.Limit), + Partial: res.Partial, + }, + } + + if err = SignMessage(s.key, resp); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return resp, nil +} + +func (s *Server) addContainerInfo(engineStats []engine.ContainerStat) []*billing.ListContainersResponse_Body_ContainerInfo { + var result []*billing.ListContainersResponse_Body_ContainerInfo + + for _, engineStat := range engineStats { + containerInfo := &billing.ListContainersResponse_Body_ContainerInfo{ + ContainerId: engineStat.ContainerID[:], + Count: &billing.ListContainersResponse_Body_ContainerInfo_Count{ + Phy: engineStat.CountPhy, + Logic: engineStat.CountLogic, + User: engineStat.CountUser, + }, + Size: &billing.ListContainersResponse_Body_ContainerInfo_Size{ + Logic: engineStat.SizeLogic, + }, + ContainerStatus: billing.ListContainersResponse_Body_ContainerInfo_UNDEFINED, + } + + cnr, err := s.cnrSrc.Get(engineStat.ContainerID) + if err != nil { + if client.IsErrContainerNotFound(err) { + existed, errWasRemoved := containercore.WasRemoved(s.cnrSrc, engineStat.ContainerID) + if errWasRemoved == nil && existed { + containerInfo.ContainerStatus = billing.ListContainersResponse_Body_ContainerInfo_DELETED + } + } + } else { + containerInfo.ContainerStatus = billing.ListContainersResponse_Body_ContainerInfo_AVAILABLE + containerInfo.Attributes = &billing.ListContainersResponse_Body_ContainerInfo_Attributes{ + OwnerWallet: cnr.Value.Owner().WalletBytes(), + Zone: cnr.Value.Attribute(containerApi.SysAttributeZone), + } + } + result = append(result, containerInfo) + } + + return result +} + +func containerListNextPageToken(engineStats []engine.ContainerStat, limit uint32) []byte { + if uint32(len(engineStats)) <= limit { + return nil + } + return engineStats[len(engineStats)-1].ContainerID[:] +} + +func convertToEngineContainerStatPrm(req *billing.ListContainersRequest) (engine.ContainerStatPrm, error) { + var result engine.ContainerStatPrm + + if len(req.GetBody().GetContainerId()) > 10000 { + return result, errInvalidContainerIDLenght + } + if len(req.GetBody().GetContainerId()) > 0 { + for idx, contIDBytes := range req.GetBody().GetContainerId() { + var contID cid.ID + if err := contID.Decode(contIDBytes); err != nil { + return result, fmt.Errorf("failed to decode container ID at index %d: %w", idx, err) + } + result.ContainerID = append(result.ContainerID, contID) + } + return result, nil + } + + result.Limit = defaultLimit + if req.GetBody().GetLimit() > maxLimit { + return result, errInvalidLimit + } + if req.GetBody().GetLimit() > 0 { + result.Limit = req.GetBody().GetLimit() + } + + if len(req.GetBody().GetNextPageToken()) > 0 { + var contID cid.ID + if err := contID.Decode(req.GetBody().GetNextPageToken()); err != nil { + return result, fmt.Errorf("invalid next page token: %w", err) + } + result.StartFromContainerID = &contID + } + + return result, nil +} diff --git a/pkg/services/billing/server/server.go b/pkg/services/billing/server/server.go new file mode 100644 index 000000000..0e01cb72d --- /dev/null +++ b/pkg/services/billing/server/server.go @@ -0,0 +1,34 @@ +package server + +import ( + "crypto/ecdsa" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine" +) + +type cfg struct { + key *ecdsa.PrivateKey + allowedKeys [][]byte + cnrSrc container.Source + se *engine.StorageEngine +} + +type Server struct { + *cfg +} + +func New(key *ecdsa.PrivateKey, + allowedKeys [][]byte, + cnrSrc container.Source, + se *engine.StorageEngine, +) *Server { + return &Server{ + cfg: &cfg{ + key: key, + allowedKeys: allowedKeys, + cnrSrc: cnrSrc, + se: se, + }, + } +} diff --git a/pkg/services/billing/server/sign.go b/pkg/services/billing/server/sign.go new file mode 100644 index 000000000..579d43baf --- /dev/null +++ b/pkg/services/billing/server/sign.go @@ -0,0 +1,92 @@ +package server + +import ( + "bytes" + "crypto/ecdsa" + "errors" + "fmt" + + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/billing" + frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto" + frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa" +) + +// SignedMessage is an interface of Control service message. +type SignedMessage interface { + ReadSignedData([]byte) ([]byte, error) + GetSignature() *billing.Signature + SetSignature(*billing.Signature) +} + +var ( + errDisallowedKey = errors.New("key is not in the allowed list") + errMissingSignature = errors.New("missing signature") + errInvalidSignature = errors.New("invalid signature") +) + +func (s *Server) isValidRequest(req SignedMessage) error { + sign := req.GetSignature() + if sign == nil { + return errMissingSignature + } + + var ( + key = sign.GetKey() + allowed = false + ) + + for i := range s.allowedKeys { + if allowed = bytes.Equal(s.allowedKeys[i], key); allowed { + break + } + } + if !allowed { + return errDisallowedKey + } + + binBody, err := req.ReadSignedData(nil) + if err != nil { + return fmt.Errorf("marshal request body: %w", err) + } + + var sigV2 refs.Signature + sigV2.SetKey(sign.GetKey()) + sigV2.SetSign(sign.GetSignature()) + sigV2.SetScheme(refs.ECDSA_SHA512) + + var sig frostfscrypto.Signature + if err := sig.ReadFromV2(sigV2); err != nil { + return fmt.Errorf("can't read signature: %w", err) + } + + if !sig.Verify(binBody) { + return errInvalidSignature + } + + return nil +} + +func SignMessage(key *ecdsa.PrivateKey, msg SignedMessage) error { + binBody, err := msg.ReadSignedData(nil) + if err != nil { + return fmt.Errorf("marshal request body: %w", err) + } + + var sig frostfscrypto.Signature + + err = sig.Calculate(frostfsecdsa.Signer(*key), binBody) + if err != nil { + return fmt.Errorf("calculate signature: %w", err) + } + + var sigV2 refs.Signature + sig.WriteToV2(&sigV2) + + var sigBilling billing.Signature + sigBilling.Key = sigV2.GetKey() + sigBilling.Signature = sigV2.GetSign() + msg.SetSignature(&sigBilling) + + return nil +} diff --git a/pkg/services/billing/service.pb.go b/pkg/services/billing/service.pb.go new file mode 100644 index 000000000..e13e7fb36 --- /dev/null +++ b/pkg/services/billing/service.pb.go @@ -0,0 +1,905 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc v4.25.0 +// source: pkg/services/billing/service.proto + +package billing + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ListContainersResponse_Body_ContainerInfo_Status int32 + +const ( + // Undefined status, default value. + ListContainersResponse_Body_ContainerInfo_UNDEFINED ListContainersResponse_Body_ContainerInfo_Status = 0 + // Container is available. + ListContainersResponse_Body_ContainerInfo_AVAILABLE ListContainersResponse_Body_ContainerInfo_Status = 1 + // Container is deleted. + ListContainersResponse_Body_ContainerInfo_DELETED ListContainersResponse_Body_ContainerInfo_Status = 2 +) + +// Enum value maps for ListContainersResponse_Body_ContainerInfo_Status. +var ( + ListContainersResponse_Body_ContainerInfo_Status_name = map[int32]string{ + 0: "UNDEFINED", + 1: "AVAILABLE", + 2: "DELETED", + } + ListContainersResponse_Body_ContainerInfo_Status_value = map[string]int32{ + "UNDEFINED": 0, + "AVAILABLE": 1, + "DELETED": 2, + } +) + +func (x ListContainersResponse_Body_ContainerInfo_Status) Enum() *ListContainersResponse_Body_ContainerInfo_Status { + p := new(ListContainersResponse_Body_ContainerInfo_Status) + *p = x + return p +} + +func (x ListContainersResponse_Body_ContainerInfo_Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ListContainersResponse_Body_ContainerInfo_Status) Descriptor() protoreflect.EnumDescriptor { + return file_pkg_services_billing_service_proto_enumTypes[0].Descriptor() +} + +func (ListContainersResponse_Body_ContainerInfo_Status) Type() protoreflect.EnumType { + return &file_pkg_services_billing_service_proto_enumTypes[0] +} + +func (x ListContainersResponse_Body_ContainerInfo_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ListContainersResponse_Body_ContainerInfo_Status.Descriptor instead. +func (ListContainersResponse_Body_ContainerInfo_Status) EnumDescriptor() ([]byte, []int) { + return file_pkg_services_billing_service_proto_rawDescGZIP(), []int{2, 0, 0, 0} +} + +// Signature of some message. +type Signature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Public key used for signing. + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // Binary signature. + Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *Signature) Reset() { + *x = Signature{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_services_billing_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Signature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Signature) ProtoMessage() {} + +func (x *Signature) ProtoReflect() protoreflect.Message { + mi := &file_pkg_services_billing_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Signature.ProtoReflect.Descriptor instead. +func (*Signature) Descriptor() ([]byte, []int) { + return file_pkg_services_billing_service_proto_rawDescGZIP(), []int{0} +} + +func (x *Signature) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +func (x *Signature) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +type ListContainersRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Body *ListContainersRequest_Body `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` + Signature *Signature `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *ListContainersRequest) Reset() { + *x = ListContainersRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_services_billing_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListContainersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListContainersRequest) ProtoMessage() {} + +func (x *ListContainersRequest) ProtoReflect() protoreflect.Message { + mi := &file_pkg_services_billing_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListContainersRequest.ProtoReflect.Descriptor instead. +func (*ListContainersRequest) Descriptor() ([]byte, []int) { + return file_pkg_services_billing_service_proto_rawDescGZIP(), []int{1} +} + +func (x *ListContainersRequest) GetBody() *ListContainersRequest_Body { + if x != nil { + return x.Body + } + return nil +} + +func (x *ListContainersRequest) GetSignature() *Signature { + if x != nil { + return x.Signature + } + return nil +} + +type ListContainersResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Body *ListContainersResponse_Body `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` + Signature *Signature `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *ListContainersResponse) Reset() { + *x = ListContainersResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_services_billing_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListContainersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListContainersResponse) ProtoMessage() {} + +func (x *ListContainersResponse) ProtoReflect() protoreflect.Message { + mi := &file_pkg_services_billing_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListContainersResponse.ProtoReflect.Descriptor instead. +func (*ListContainersResponse) Descriptor() ([]byte, []int) { + return file_pkg_services_billing_service_proto_rawDescGZIP(), []int{2} +} + +func (x *ListContainersResponse) GetBody() *ListContainersResponse_Body { + if x != nil { + return x.Body + } + return nil +} + +func (x *ListContainersResponse) GetSignature() *Signature { + if x != nil { + return x.Signature + } + return nil +} + +type ListContainersRequest_Body struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Container IDs to get container metrics. Not required. Maximum count is + // 10000 items. + ContainerId [][]byte `protobuf:"bytes,1,rep,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` + // Max items count in the response. Not required. Default value is 1000. Not + // applicable if container_id is specified. Maximum value is 10000. + Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + // Next page token used to continue listing. Not required. If null or empty, + // listing starts from the beginning. + NextPageToken []byte `protobuf:"bytes,3,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +} + +func (x *ListContainersRequest_Body) Reset() { + *x = ListContainersRequest_Body{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_services_billing_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListContainersRequest_Body) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListContainersRequest_Body) ProtoMessage() {} + +func (x *ListContainersRequest_Body) ProtoReflect() protoreflect.Message { + mi := &file_pkg_services_billing_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListContainersRequest_Body.ProtoReflect.Descriptor instead. +func (*ListContainersRequest_Body) Descriptor() ([]byte, []int) { + return file_pkg_services_billing_service_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *ListContainersRequest_Body) GetContainerId() [][]byte { + if x != nil { + return x.ContainerId + } + return nil +} + +func (x *ListContainersRequest_Body) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListContainersRequest_Body) GetNextPageToken() []byte { + if x != nil { + return x.NextPageToken + } + return nil +} + +type ListContainersResponse_Body struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Result []*ListContainersResponse_Body_ContainerInfo `protobuf:"bytes,1,rep,name=result,proto3" json:"result,omitempty"` + // Next page token used to get next batch. If returned value is null or + // empty, then listing completed. + NextPageToken []byte `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + // Returned result is partial. This could happend if some shards are in + // degraded mode or returned an error. + Partial bool `protobuf:"varint,3,opt,name=partial,proto3" json:"partial,omitempty"` +} + +func (x *ListContainersResponse_Body) Reset() { + *x = ListContainersResponse_Body{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_services_billing_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListContainersResponse_Body) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListContainersResponse_Body) ProtoMessage() {} + +func (x *ListContainersResponse_Body) ProtoReflect() protoreflect.Message { + mi := &file_pkg_services_billing_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListContainersResponse_Body.ProtoReflect.Descriptor instead. +func (*ListContainersResponse_Body) Descriptor() ([]byte, []int) { + return file_pkg_services_billing_service_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *ListContainersResponse_Body) GetResult() []*ListContainersResponse_Body_ContainerInfo { + if x != nil { + return x.Result + } + return nil +} + +func (x *ListContainersResponse_Body) GetNextPageToken() []byte { + if x != nil { + return x.NextPageToken + } + return nil +} + +func (x *ListContainersResponse_Body) GetPartial() bool { + if x != nil { + return x.Partial + } + return false +} + +type ListContainersResponse_Body_ContainerInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Container ID. + ContainerId []byte `protobuf:"bytes,1,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` + // Container status. + ContainerStatus ListContainersResponse_Body_ContainerInfo_Status `protobuf:"varint,2,opt,name=container_status,json=containerStatus,proto3,enum=billing.ListContainersResponse_Body_ContainerInfo_Status" json:"container_status,omitempty"` + // Container attributes. May be null if container attributes are + // unavailable for current moment or container already deleted. + Attributes *ListContainersResponse_Body_ContainerInfo_Attributes `protobuf:"bytes,3,opt,name=attributes,proto3" json:"attributes,omitempty"` + // Count of the objects in container. + Count *ListContainersResponse_Body_ContainerInfo_Count `protobuf:"bytes,4,opt,name=count,proto3" json:"count,omitempty"` + // Size of the objects in container. + Size *ListContainersResponse_Body_ContainerInfo_Size `protobuf:"bytes,5,opt,name=size,proto3" json:"size,omitempty"` +} + +func (x *ListContainersResponse_Body_ContainerInfo) Reset() { + *x = ListContainersResponse_Body_ContainerInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_services_billing_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListContainersResponse_Body_ContainerInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListContainersResponse_Body_ContainerInfo) ProtoMessage() {} + +func (x *ListContainersResponse_Body_ContainerInfo) ProtoReflect() protoreflect.Message { + mi := &file_pkg_services_billing_service_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListContainersResponse_Body_ContainerInfo.ProtoReflect.Descriptor instead. +func (*ListContainersResponse_Body_ContainerInfo) Descriptor() ([]byte, []int) { + return file_pkg_services_billing_service_proto_rawDescGZIP(), []int{2, 0, 0} +} + +func (x *ListContainersResponse_Body_ContainerInfo) GetContainerId() []byte { + if x != nil { + return x.ContainerId + } + return nil +} + +func (x *ListContainersResponse_Body_ContainerInfo) GetContainerStatus() ListContainersResponse_Body_ContainerInfo_Status { + if x != nil { + return x.ContainerStatus + } + return ListContainersResponse_Body_ContainerInfo_UNDEFINED +} + +func (x *ListContainersResponse_Body_ContainerInfo) GetAttributes() *ListContainersResponse_Body_ContainerInfo_Attributes { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *ListContainersResponse_Body_ContainerInfo) GetCount() *ListContainersResponse_Body_ContainerInfo_Count { + if x != nil { + return x.Count + } + return nil +} + +func (x *ListContainersResponse_Body_ContainerInfo) GetSize() *ListContainersResponse_Body_ContainerInfo_Size { + if x != nil { + return x.Size + } + return nil +} + +type ListContainersResponse_Body_ContainerInfo_Count struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Phy uint64 `protobuf:"varint,1,opt,name=phy,proto3" json:"phy,omitempty"` + Logic uint64 `protobuf:"varint,2,opt,name=logic,proto3" json:"logic,omitempty"` + User uint64 `protobuf:"varint,3,opt,name=user,proto3" json:"user,omitempty"` +} + +func (x *ListContainersResponse_Body_ContainerInfo_Count) Reset() { + *x = ListContainersResponse_Body_ContainerInfo_Count{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_services_billing_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListContainersResponse_Body_ContainerInfo_Count) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListContainersResponse_Body_ContainerInfo_Count) ProtoMessage() {} + +func (x *ListContainersResponse_Body_ContainerInfo_Count) ProtoReflect() protoreflect.Message { + mi := &file_pkg_services_billing_service_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListContainersResponse_Body_ContainerInfo_Count.ProtoReflect.Descriptor instead. +func (*ListContainersResponse_Body_ContainerInfo_Count) Descriptor() ([]byte, []int) { + return file_pkg_services_billing_service_proto_rawDescGZIP(), []int{2, 0, 0, 0} +} + +func (x *ListContainersResponse_Body_ContainerInfo_Count) GetPhy() uint64 { + if x != nil { + return x.Phy + } + return 0 +} + +func (x *ListContainersResponse_Body_ContainerInfo_Count) GetLogic() uint64 { + if x != nil { + return x.Logic + } + return 0 +} + +func (x *ListContainersResponse_Body_ContainerInfo_Count) GetUser() uint64 { + if x != nil { + return x.User + } + return 0 +} + +type ListContainersResponse_Body_ContainerInfo_Size struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Logic uint64 `protobuf:"varint,1,opt,name=logic,proto3" json:"logic,omitempty"` +} + +func (x *ListContainersResponse_Body_ContainerInfo_Size) Reset() { + *x = ListContainersResponse_Body_ContainerInfo_Size{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_services_billing_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListContainersResponse_Body_ContainerInfo_Size) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListContainersResponse_Body_ContainerInfo_Size) ProtoMessage() {} + +func (x *ListContainersResponse_Body_ContainerInfo_Size) ProtoReflect() protoreflect.Message { + mi := &file_pkg_services_billing_service_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListContainersResponse_Body_ContainerInfo_Size.ProtoReflect.Descriptor instead. +func (*ListContainersResponse_Body_ContainerInfo_Size) Descriptor() ([]byte, []int) { + return file_pkg_services_billing_service_proto_rawDescGZIP(), []int{2, 0, 0, 1} +} + +func (x *ListContainersResponse_Body_ContainerInfo_Size) GetLogic() uint64 { + if x != nil { + return x.Logic + } + return 0 +} + +type ListContainersResponse_Body_ContainerInfo_Attributes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Container owner's wallet bytes. + OwnerWallet []byte `protobuf:"bytes,1,opt,name=owner_wallet,json=ownerWallet,proto3" json:"owner_wallet,omitempty"` + // Container zone. + Zone string `protobuf:"bytes,2,opt,name=zone,proto3" json:"zone,omitempty"` +} + +func (x *ListContainersResponse_Body_ContainerInfo_Attributes) Reset() { + *x = ListContainersResponse_Body_ContainerInfo_Attributes{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_services_billing_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListContainersResponse_Body_ContainerInfo_Attributes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListContainersResponse_Body_ContainerInfo_Attributes) ProtoMessage() {} + +func (x *ListContainersResponse_Body_ContainerInfo_Attributes) ProtoReflect() protoreflect.Message { + mi := &file_pkg_services_billing_service_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListContainersResponse_Body_ContainerInfo_Attributes.ProtoReflect.Descriptor instead. +func (*ListContainersResponse_Body_ContainerInfo_Attributes) Descriptor() ([]byte, []int) { + return file_pkg_services_billing_service_proto_rawDescGZIP(), []int{2, 0, 0, 2} +} + +func (x *ListContainersResponse_Body_ContainerInfo_Attributes) GetOwnerWallet() []byte { + if x != nil { + return x.OwnerWallet + } + return nil +} + +func (x *ListContainersResponse_Body_ContainerInfo_Attributes) GetZone() string { + if x != nil { + return x.Zone + } + return "" +} + +var File_pkg_services_billing_service_proto protoreflect.FileDescriptor + +var file_pkg_services_billing_service_proto_rawDesc = []byte{ + 0x0a, 0x22, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x62, + 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x3b, 0x0a, + 0x09, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xeb, 0x01, 0x0a, 0x15, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x30, 0x0a, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x1a, + 0x67, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0b, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, + 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8f, 0x07, 0x0a, 0x16, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x24, 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x30, 0x0a, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x1a, + 0x88, 0x06, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x4a, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, + 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x43, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6e, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07, + 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, + 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x1a, 0xf1, 0x04, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x64, 0x0a, 0x10, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x5d, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x12, 0x4e, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x38, 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, + 0x6e, 0x66, 0x6f, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x4b, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, + 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6e, + 0x66, 0x6f, 0x2e, 0x53, 0x69, 0x7a, 0x65, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x1a, 0x43, 0x0a, + 0x05, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x68, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x03, 0x70, 0x68, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x6f, 0x67, 0x69, + 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6c, 0x6f, 0x67, 0x69, 0x63, 0x12, 0x12, + 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x75, 0x73, + 0x65, 0x72, 0x1a, 0x1c, 0x0a, 0x04, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x6f, + 0x67, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6c, 0x6f, 0x67, 0x69, 0x63, + 0x1a, 0x43, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x21, + 0x0a, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x57, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x22, 0x33, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, + 0x0a, 0x09, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, + 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x02, 0x32, 0x63, 0x0a, 0x0e, 0x42, 0x69, + 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x1e, + 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, + 0x2e, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x41, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x2e, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2e, 0x69, + 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, 0x75, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x4c, 0x61, 0x62, + 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x70, 0x6b, + 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x62, 0x69, 0x6c, 0x6c, 0x69, + 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_pkg_services_billing_service_proto_rawDescOnce sync.Once + file_pkg_services_billing_service_proto_rawDescData = file_pkg_services_billing_service_proto_rawDesc +) + +func file_pkg_services_billing_service_proto_rawDescGZIP() []byte { + file_pkg_services_billing_service_proto_rawDescOnce.Do(func() { + file_pkg_services_billing_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_services_billing_service_proto_rawDescData) + }) + return file_pkg_services_billing_service_proto_rawDescData +} + +var file_pkg_services_billing_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_pkg_services_billing_service_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_pkg_services_billing_service_proto_goTypes = []interface{}{ + (ListContainersResponse_Body_ContainerInfo_Status)(0), // 0: billing.ListContainersResponse.Body.ContainerInfo.Status + (*Signature)(nil), // 1: billing.Signature + (*ListContainersRequest)(nil), // 2: billing.ListContainersRequest + (*ListContainersResponse)(nil), // 3: billing.ListContainersResponse + (*ListContainersRequest_Body)(nil), // 4: billing.ListContainersRequest.Body + (*ListContainersResponse_Body)(nil), // 5: billing.ListContainersResponse.Body + (*ListContainersResponse_Body_ContainerInfo)(nil), // 6: billing.ListContainersResponse.Body.ContainerInfo + (*ListContainersResponse_Body_ContainerInfo_Count)(nil), // 7: billing.ListContainersResponse.Body.ContainerInfo.Count + (*ListContainersResponse_Body_ContainerInfo_Size)(nil), // 8: billing.ListContainersResponse.Body.ContainerInfo.Size + (*ListContainersResponse_Body_ContainerInfo_Attributes)(nil), // 9: billing.ListContainersResponse.Body.ContainerInfo.Attributes +} +var file_pkg_services_billing_service_proto_depIdxs = []int32{ + 4, // 0: billing.ListContainersRequest.body:type_name -> billing.ListContainersRequest.Body + 1, // 1: billing.ListContainersRequest.signature:type_name -> billing.Signature + 5, // 2: billing.ListContainersResponse.body:type_name -> billing.ListContainersResponse.Body + 1, // 3: billing.ListContainersResponse.signature:type_name -> billing.Signature + 6, // 4: billing.ListContainersResponse.Body.result:type_name -> billing.ListContainersResponse.Body.ContainerInfo + 0, // 5: billing.ListContainersResponse.Body.ContainerInfo.container_status:type_name -> billing.ListContainersResponse.Body.ContainerInfo.Status + 9, // 6: billing.ListContainersResponse.Body.ContainerInfo.attributes:type_name -> billing.ListContainersResponse.Body.ContainerInfo.Attributes + 7, // 7: billing.ListContainersResponse.Body.ContainerInfo.count:type_name -> billing.ListContainersResponse.Body.ContainerInfo.Count + 8, // 8: billing.ListContainersResponse.Body.ContainerInfo.size:type_name -> billing.ListContainersResponse.Body.ContainerInfo.Size + 2, // 9: billing.BillingService.ListContainers:input_type -> billing.ListContainersRequest + 3, // 10: billing.BillingService.ListContainers:output_type -> billing.ListContainersResponse + 10, // [10:11] is the sub-list for method output_type + 9, // [9:10] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_pkg_services_billing_service_proto_init() } +func file_pkg_services_billing_service_proto_init() { + if File_pkg_services_billing_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_pkg_services_billing_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Signature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_services_billing_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListContainersRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_services_billing_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListContainersResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_services_billing_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListContainersRequest_Body); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_services_billing_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListContainersResponse_Body); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_services_billing_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListContainersResponse_Body_ContainerInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_services_billing_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListContainersResponse_Body_ContainerInfo_Count); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_services_billing_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListContainersResponse_Body_ContainerInfo_Size); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_services_billing_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListContainersResponse_Body_ContainerInfo_Attributes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_pkg_services_billing_service_proto_rawDesc, + NumEnums: 1, + NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_pkg_services_billing_service_proto_goTypes, + DependencyIndexes: file_pkg_services_billing_service_proto_depIdxs, + EnumInfos: file_pkg_services_billing_service_proto_enumTypes, + MessageInfos: file_pkg_services_billing_service_proto_msgTypes, + }.Build() + File_pkg_services_billing_service_proto = out.File + file_pkg_services_billing_service_proto_rawDesc = nil + file_pkg_services_billing_service_proto_goTypes = nil + file_pkg_services_billing_service_proto_depIdxs = nil +} diff --git a/pkg/services/billing/service.proto b/pkg/services/billing/service.proto new file mode 100644 index 000000000..6c06cc0f6 --- /dev/null +++ b/pkg/services/billing/service.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +package billing; + +option go_package = "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/billing"; + +// `BillingService` provides an interface to get usage statistics. +service BillingService { + // Returns list that contains container usage statistic. + rpc ListContainers(ListContainersRequest) returns (ListContainersResponse); +} + +// Signature of some message. +message Signature { + // Public key used for signing. + bytes key = 1; + + // Binary signature. + bytes signature = 2; +} + +message ListContainersRequest { + message Body { + // Container IDs to get container metrics. Not required. Maximum count is + // 10000 items. + repeated bytes container_id = 1; + // Max items count in the response. Not required. Default value is 1000. Not + // applicable if container_id is specified. Maximum value is 10000. + uint32 limit = 2; + // Next page token used to continue listing. Not required. If null or empty, + // listing starts from the beginning. + bytes next_page_token = 3; + } + + Body body = 1; + Signature signature = 2; +} + +message ListContainersResponse { + message Body { + message ContainerInfo { + message Count { + uint64 phy = 1; + uint64 logic = 2; + uint64 user = 3; + } + message Size { uint64 logic = 1; } + message Attributes { + // Container owner's wallet bytes. + bytes owner_wallet = 1; + // Container zone. + string zone = 2; + } + enum Status { + // Undefined status, default value. + UNDEFINED = 0; + // Container is available. + AVAILABLE = 1; + // Container is deleted. + DELETED = 2; + } + + // Container ID. + bytes container_id = 1; + // Container status. + Status container_status = 2; + // Container attributes. May be null if container attributes are + // unavailable for current moment or container already deleted. + Attributes attributes = 3; + // Count of the objects in container. + Count count = 4; + // Size of the objects in container. + Size size = 5; + } + + repeated ContainerInfo result = 1; + // Next page token used to get next batch. If returned value is null or + // empty, then listing completed. + bytes next_page_token = 2; + // Returned result is partial. This could happend if some shards are in + // degraded mode or returned an error. + bool partial = 3; + } + + Body body = 1; + Signature signature = 2; +} \ No newline at end of file diff --git a/pkg/services/billing/service_frostfs.pb.go b/pkg/services/billing/service_frostfs.pb.go new file mode 100644 index 000000000..c5ada111f --- /dev/null +++ b/pkg/services/billing/service_frostfs.pb.go @@ -0,0 +1,360 @@ +// Code generated by protoc-gen-go-frostfs. DO NOT EDIT. + +package billing + +import "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/proto" + +// StableSize returns the size of x in protobuf format. +// +// Structures with the same field values have the same binary size. +func (x *Signature) StableSize() (size int) { + if x == nil { + return 0 + } + size += proto.BytesSize(1, x.Key) + size += proto.BytesSize(2, x.Signature) + return size +} + +// StableMarshal marshals x in protobuf binary format with stable field order. +// +// If buffer length is less than x.StableSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same binary format. +func (x *Signature) StableMarshal(buf []byte) []byte { + if x == nil { + return []byte{} + } + if buf == nil { + buf = make([]byte, x.StableSize()) + } + var offset int + offset += proto.BytesMarshal(1, buf[offset:], x.Key) + offset += proto.BytesMarshal(2, buf[offset:], x.Signature) + return buf +} + +// StableSize returns the size of x in protobuf format. +// +// Structures with the same field values have the same binary size. +func (x *ListContainersRequest_Body) StableSize() (size int) { + if x == nil { + return 0 + } + size += proto.RepeatedBytesSize(1, x.ContainerId) + size += proto.UInt32Size(2, x.Limit) + size += proto.BytesSize(3, x.NextPageToken) + return size +} + +// StableMarshal marshals x in protobuf binary format with stable field order. +// +// If buffer length is less than x.StableSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same binary format. +func (x *ListContainersRequest_Body) StableMarshal(buf []byte) []byte { + if x == nil { + return []byte{} + } + if buf == nil { + buf = make([]byte, x.StableSize()) + } + var offset int + offset += proto.RepeatedBytesMarshal(1, buf[offset:], x.ContainerId) + offset += proto.UInt32Marshal(2, buf[offset:], x.Limit) + offset += proto.BytesMarshal(3, buf[offset:], x.NextPageToken) + return buf +} + +// StableSize returns the size of x in protobuf format. +// +// Structures with the same field values have the same binary size. +func (x *ListContainersRequest) StableSize() (size int) { + if x == nil { + return 0 + } + size += proto.NestedStructureSize(1, x.Body) + size += proto.NestedStructureSize(2, x.Signature) + return size +} + +// StableMarshal marshals x in protobuf binary format with stable field order. +// +// If buffer length is less than x.StableSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same binary format. +func (x *ListContainersRequest) StableMarshal(buf []byte) []byte { + if x == nil { + return []byte{} + } + if buf == nil { + buf = make([]byte, x.StableSize()) + } + var offset int + offset += proto.NestedStructureMarshal(1, buf[offset:], x.Body) + offset += proto.NestedStructureMarshal(2, buf[offset:], x.Signature) + return buf +} + +// ReadSignedData fills buf with signed data of x. +// If buffer length is less than x.SignedDataSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same signed data. +func (x *ListContainersRequest) SignedDataSize() int { + return x.GetBody().StableSize() +} + +// SignedDataSize returns size of the request signed data in bytes. +// +// Structures with the same field values have the same signed data size. +func (x *ListContainersRequest) ReadSignedData(buf []byte) ([]byte, error) { + return x.GetBody().StableMarshal(buf), nil +} + +func (x *ListContainersRequest) SetSignature(sig *Signature) { + x.Signature = sig +} + +// StableSize returns the size of x in protobuf format. +// +// Structures with the same field values have the same binary size. +func (x *ListContainersResponse_Body_ContainerInfo_Count) StableSize() (size int) { + if x == nil { + return 0 + } + size += proto.UInt64Size(1, x.Phy) + size += proto.UInt64Size(2, x.Logic) + size += proto.UInt64Size(3, x.User) + return size +} + +// StableMarshal marshals x in protobuf binary format with stable field order. +// +// If buffer length is less than x.StableSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same binary format. +func (x *ListContainersResponse_Body_ContainerInfo_Count) StableMarshal(buf []byte) []byte { + if x == nil { + return []byte{} + } + if buf == nil { + buf = make([]byte, x.StableSize()) + } + var offset int + offset += proto.UInt64Marshal(1, buf[offset:], x.Phy) + offset += proto.UInt64Marshal(2, buf[offset:], x.Logic) + offset += proto.UInt64Marshal(3, buf[offset:], x.User) + return buf +} + +// StableSize returns the size of x in protobuf format. +// +// Structures with the same field values have the same binary size. +func (x *ListContainersResponse_Body_ContainerInfo_Size) StableSize() (size int) { + if x == nil { + return 0 + } + size += proto.UInt64Size(1, x.Logic) + return size +} + +// StableMarshal marshals x in protobuf binary format with stable field order. +// +// If buffer length is less than x.StableSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same binary format. +func (x *ListContainersResponse_Body_ContainerInfo_Size) StableMarshal(buf []byte) []byte { + if x == nil { + return []byte{} + } + if buf == nil { + buf = make([]byte, x.StableSize()) + } + var offset int + offset += proto.UInt64Marshal(1, buf[offset:], x.Logic) + return buf +} + +// StableSize returns the size of x in protobuf format. +// +// Structures with the same field values have the same binary size. +func (x *ListContainersResponse_Body_ContainerInfo_Attributes) StableSize() (size int) { + if x == nil { + return 0 + } + size += proto.BytesSize(1, x.OwnerWallet) + size += proto.StringSize(2, x.Zone) + return size +} + +// StableMarshal marshals x in protobuf binary format with stable field order. +// +// If buffer length is less than x.StableSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same binary format. +func (x *ListContainersResponse_Body_ContainerInfo_Attributes) StableMarshal(buf []byte) []byte { + if x == nil { + return []byte{} + } + if buf == nil { + buf = make([]byte, x.StableSize()) + } + var offset int + offset += proto.BytesMarshal(1, buf[offset:], x.OwnerWallet) + offset += proto.StringMarshal(2, buf[offset:], x.Zone) + return buf +} + +// StableSize returns the size of x in protobuf format. +// +// Structures with the same field values have the same binary size. +func (x *ListContainersResponse_Body_ContainerInfo) StableSize() (size int) { + if x == nil { + return 0 + } + size += proto.BytesSize(1, x.ContainerId) + size += proto.EnumSize(2, int32(x.ContainerStatus)) + size += proto.NestedStructureSize(3, x.Attributes) + size += proto.NestedStructureSize(4, x.Count) + size += proto.NestedStructureSize(5, x.Size) + return size +} + +// StableMarshal marshals x in protobuf binary format with stable field order. +// +// If buffer length is less than x.StableSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same binary format. +func (x *ListContainersResponse_Body_ContainerInfo) StableMarshal(buf []byte) []byte { + if x == nil { + return []byte{} + } + if buf == nil { + buf = make([]byte, x.StableSize()) + } + var offset int + offset += proto.BytesMarshal(1, buf[offset:], x.ContainerId) + offset += proto.EnumMarshal(2, buf[offset:], int32(x.ContainerStatus)) + offset += proto.NestedStructureMarshal(3, buf[offset:], x.Attributes) + offset += proto.NestedStructureMarshal(4, buf[offset:], x.Count) + offset += proto.NestedStructureMarshal(5, buf[offset:], x.Size) + return buf +} + +// StableSize returns the size of x in protobuf format. +// +// Structures with the same field values have the same binary size. +func (x *ListContainersResponse_Body) StableSize() (size int) { + if x == nil { + return 0 + } + for i := range x.Result { + size += proto.NestedStructureSize(1, x.Result[i]) + } + size += proto.BytesSize(2, x.NextPageToken) + size += proto.BoolSize(3, x.Partial) + return size +} + +// StableMarshal marshals x in protobuf binary format with stable field order. +// +// If buffer length is less than x.StableSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same binary format. +func (x *ListContainersResponse_Body) StableMarshal(buf []byte) []byte { + if x == nil { + return []byte{} + } + if buf == nil { + buf = make([]byte, x.StableSize()) + } + var offset int + for i := range x.Result { + offset += proto.NestedStructureMarshal(1, buf[offset:], x.Result[i]) + } + offset += proto.BytesMarshal(2, buf[offset:], x.NextPageToken) + offset += proto.BoolMarshal(3, buf[offset:], x.Partial) + return buf +} + +// StableSize returns the size of x in protobuf format. +// +// Structures with the same field values have the same binary size. +func (x *ListContainersResponse) StableSize() (size int) { + if x == nil { + return 0 + } + size += proto.NestedStructureSize(1, x.Body) + size += proto.NestedStructureSize(2, x.Signature) + return size +} + +// StableMarshal marshals x in protobuf binary format with stable field order. +// +// If buffer length is less than x.StableSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same binary format. +func (x *ListContainersResponse) StableMarshal(buf []byte) []byte { + if x == nil { + return []byte{} + } + if buf == nil { + buf = make([]byte, x.StableSize()) + } + var offset int + offset += proto.NestedStructureMarshal(1, buf[offset:], x.Body) + offset += proto.NestedStructureMarshal(2, buf[offset:], x.Signature) + return buf +} + +// ReadSignedData fills buf with signed data of x. +// If buffer length is less than x.SignedDataSize(), new buffer is allocated. +// +// Returns any error encountered which did not allow writing the data completely. +// Otherwise, returns the buffer in which the data is written. +// +// Structures with the same field values have the same signed data. +func (x *ListContainersResponse) SignedDataSize() int { + return x.GetBody().StableSize() +} + +// SignedDataSize returns size of the request signed data in bytes. +// +// Structures with the same field values have the same signed data size. +func (x *ListContainersResponse) ReadSignedData(buf []byte) ([]byte, error) { + return x.GetBody().StableMarshal(buf), nil +} + +func (x *ListContainersResponse) SetSignature(sig *Signature) { + x.Signature = sig +} diff --git a/pkg/services/billing/service_grpc.pb.go b/pkg/services/billing/service_grpc.pb.go new file mode 100644 index 000000000..be81d72e0 --- /dev/null +++ b/pkg/services/billing/service_grpc.pb.go @@ -0,0 +1,109 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.25.0 +// source: pkg/services/billing/service.proto + +package billing + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + BillingService_ListContainers_FullMethodName = "/billing.BillingService/ListContainers" +) + +// BillingServiceClient is the client API for BillingService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type BillingServiceClient interface { + // Returns list that contains container usage statistic. + ListContainers(ctx context.Context, in *ListContainersRequest, opts ...grpc.CallOption) (*ListContainersResponse, error) +} + +type billingServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewBillingServiceClient(cc grpc.ClientConnInterface) BillingServiceClient { + return &billingServiceClient{cc} +} + +func (c *billingServiceClient) ListContainers(ctx context.Context, in *ListContainersRequest, opts ...grpc.CallOption) (*ListContainersResponse, error) { + out := new(ListContainersResponse) + err := c.cc.Invoke(ctx, BillingService_ListContainers_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// BillingServiceServer is the server API for BillingService service. +// All implementations should embed UnimplementedBillingServiceServer +// for forward compatibility +type BillingServiceServer interface { + // Returns list that contains container usage statistic. + ListContainers(context.Context, *ListContainersRequest) (*ListContainersResponse, error) +} + +// UnimplementedBillingServiceServer should be embedded to have forward compatible implementations. +type UnimplementedBillingServiceServer struct { +} + +func (UnimplementedBillingServiceServer) ListContainers(context.Context, *ListContainersRequest) (*ListContainersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListContainers not implemented") +} + +// UnsafeBillingServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to BillingServiceServer will +// result in compilation errors. +type UnsafeBillingServiceServer interface { + mustEmbedUnimplementedBillingServiceServer() +} + +func RegisterBillingServiceServer(s grpc.ServiceRegistrar, srv BillingServiceServer) { + s.RegisterService(&BillingService_ServiceDesc, srv) +} + +func _BillingService_ListContainers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListContainersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BillingServiceServer).ListContainers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: BillingService_ListContainers_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BillingServiceServer).ListContainers(ctx, req.(*ListContainersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// BillingService_ServiceDesc is the grpc.ServiceDesc for BillingService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var BillingService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "billing.BillingService", + HandlerType: (*BillingServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListContainers", + Handler: _BillingService_ListContainers_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "pkg/services/billing/service.proto", +}