2019-06-19 13:02:37 +01:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package satellitedb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-06-21 20:14:34 +01:00
|
|
|
"database/sql"
|
2020-07-14 14:04:38 +01:00
|
|
|
"errors"
|
2019-06-25 21:58:38 +01:00
|
|
|
"time"
|
2019-06-19 13:02:37 +01:00
|
|
|
|
2019-06-25 21:58:38 +01:00
|
|
|
"github.com/zeebo/errs"
|
2019-06-19 13:02:37 +01:00
|
|
|
|
2020-03-30 10:08:50 +01:00
|
|
|
"storj.io/common/uuid"
|
2019-06-19 13:02:37 +01:00
|
|
|
"storj.io/storj/satellite/attribution"
|
2020-01-15 02:29:51 +00:00
|
|
|
"storj.io/storj/satellite/satellitedb/dbx"
|
2019-06-19 13:02:37 +01:00
|
|
|
)
|
|
|
|
|
2019-06-25 21:58:38 +01:00
|
|
|
const (
|
|
|
|
valueAttrQuery = `
|
|
|
|
-- A union of both the storage tally and bandwidth rollups.
|
|
|
|
-- Should be 1 row per project/bucket by partner within the timeframe specified
|
|
|
|
SELECT
|
|
|
|
o.partner_id as partner_id,
|
2021-09-23 00:38:18 +01:00
|
|
|
o.user_agent as user_agent,
|
2019-06-25 21:58:38 +01:00
|
|
|
o.project_id as project_id,
|
|
|
|
o.bucket_name as bucket_name,
|
2021-07-01 12:29:25 +01:00
|
|
|
SUM(o.total) / SUM(o.hours) as total,
|
2019-06-25 21:58:38 +01:00
|
|
|
SUM(o.remote) / SUM(o.hours) as remote,
|
|
|
|
SUM(o.inline) / SUM(o.hours) as inline,
|
|
|
|
SUM(o.settled) as settled
|
|
|
|
FROM
|
|
|
|
(
|
|
|
|
-- SUM the storage and hours
|
|
|
|
-- Hours are used to calculate byte hours above
|
|
|
|
SELECT
|
|
|
|
bsti.partner_id as partner_id,
|
2021-09-23 00:38:18 +01:00
|
|
|
bsti.user_agent as user_agent,
|
2019-06-25 21:58:38 +01:00
|
|
|
bsto.project_id as project_id,
|
2021-07-01 12:29:25 +01:00
|
|
|
bsto.bucket_name as bucket_name,
|
|
|
|
SUM(bsto.total_bytes) as total,
|
2019-06-25 21:58:38 +01:00
|
|
|
SUM(bsto.remote) as remote,
|
|
|
|
SUM(bsto.inline) as inline,
|
|
|
|
0 as settled,
|
|
|
|
count(1) as hours
|
|
|
|
FROM
|
|
|
|
(
|
|
|
|
-- Collapse entries by the latest record in the hour
|
|
|
|
-- If there are more than 1 records within the hour, only the latest will be considered
|
|
|
|
SELECT
|
|
|
|
va.partner_id,
|
2021-09-23 00:38:18 +01:00
|
|
|
va.user_agent,
|
2019-10-18 22:27:57 +01:00
|
|
|
date_trunc('hour', bst.interval_start) as hours,
|
2019-06-25 21:58:38 +01:00
|
|
|
bst.project_id,
|
|
|
|
bst.bucket_name,
|
|
|
|
MAX(bst.interval_start) as max_interval
|
|
|
|
FROM
|
|
|
|
bucket_storage_tallies bst
|
|
|
|
LEFT OUTER JOIN value_attributions va ON (
|
|
|
|
bst.project_id = va.project_id
|
|
|
|
AND bst.bucket_name = va.bucket_name
|
|
|
|
)
|
|
|
|
WHERE
|
|
|
|
va.partner_id = ?
|
2021-09-23 00:38:18 +01:00
|
|
|
AND va.user_agent = ?
|
2019-06-25 21:58:38 +01:00
|
|
|
AND bst.interval_start >= ?
|
|
|
|
AND bst.interval_start < ?
|
|
|
|
GROUP BY
|
|
|
|
va.partner_id,
|
2021-09-23 00:38:18 +01:00
|
|
|
va.user_agent,
|
2019-06-25 21:58:38 +01:00
|
|
|
bst.project_id,
|
|
|
|
bst.bucket_name,
|
2019-12-03 05:20:20 +00:00
|
|
|
date_trunc('hour', bst.interval_start)
|
2019-06-25 21:58:38 +01:00
|
|
|
ORDER BY
|
|
|
|
max_interval DESC
|
|
|
|
) bsti
|
|
|
|
LEFT JOIN bucket_storage_tallies bsto ON (
|
|
|
|
bsto.project_id = bsti.project_id
|
|
|
|
AND bsto.bucket_name = bsti.bucket_name
|
|
|
|
AND bsto.interval_start = bsti.max_interval
|
|
|
|
)
|
|
|
|
GROUP BY
|
|
|
|
bsti.partner_id,
|
2021-09-23 00:38:18 +01:00
|
|
|
bsti.user_agent,
|
2019-06-25 21:58:38 +01:00
|
|
|
bsto.project_id,
|
|
|
|
bsto.bucket_name
|
|
|
|
UNION
|
2021-09-23 00:38:18 +01:00
|
|
|
-- SUM the bandwidth for the timeframe specified grouping by the partner_id, user_agent, project_id, and bucket_name
|
2019-06-25 21:58:38 +01:00
|
|
|
SELECT
|
|
|
|
va.partner_id as partner_id,
|
2021-09-23 00:38:18 +01:00
|
|
|
va.user_agent as user_agent,
|
2019-06-25 21:58:38 +01:00
|
|
|
bbr.project_id as project_id,
|
|
|
|
bbr.bucket_name as bucket_name,
|
2021-07-01 12:29:25 +01:00
|
|
|
0 as total,
|
2019-06-25 21:58:38 +01:00
|
|
|
0 as remote,
|
|
|
|
0 as inline,
|
2019-12-03 05:20:20 +00:00
|
|
|
SUM(settled)::integer as settled,
|
2019-06-25 21:58:38 +01:00
|
|
|
NULL as hours
|
|
|
|
FROM
|
|
|
|
bucket_bandwidth_rollups bbr
|
|
|
|
LEFT OUTER JOIN value_attributions va ON (
|
|
|
|
bbr.project_id = va.project_id
|
|
|
|
AND bbr.bucket_name = va.bucket_name
|
|
|
|
)
|
|
|
|
WHERE
|
|
|
|
va.partner_id = ?
|
2021-09-23 00:38:18 +01:00
|
|
|
AND va.user_agent = ?
|
2019-06-25 21:58:38 +01:00
|
|
|
AND bbr.interval_start >= ?
|
|
|
|
AND bbr.interval_start < ?
|
|
|
|
AND bbr.action = 2
|
|
|
|
GROUP BY
|
|
|
|
va.partner_id,
|
2021-09-23 00:38:18 +01:00
|
|
|
va.user_agent,
|
2019-06-25 21:58:38 +01:00
|
|
|
bbr.project_id,
|
|
|
|
bbr.bucket_name
|
|
|
|
) AS o
|
|
|
|
GROUP BY
|
|
|
|
o.partner_id,
|
2021-09-23 00:38:18 +01:00
|
|
|
o.user_agent,
|
2019-06-25 21:58:38 +01:00
|
|
|
o.project_id,
|
|
|
|
o.bucket_name;
|
|
|
|
`
|
|
|
|
)
|
|
|
|
|
2019-06-19 13:02:37 +01:00
|
|
|
type attributionDB struct {
|
2019-12-14 02:29:54 +00:00
|
|
|
db *satelliteDB
|
2019-06-19 13:02:37 +01:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Get reads the partner info.
|
2019-06-19 13:02:37 +01:00
|
|
|
func (keys *attributionDB) Get(ctx context.Context, projectID uuid.UUID, bucketName []byte) (info *attribution.Info, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
dbxInfo, err := keys.db.Get_ValueAttribution_By_ProjectId_And_BucketName(ctx,
|
|
|
|
dbx.ValueAttribution_ProjectId(projectID[:]),
|
|
|
|
dbx.ValueAttribution_BucketName(bucketName),
|
|
|
|
)
|
2020-07-14 14:04:38 +01:00
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
2019-08-21 17:30:29 +01:00
|
|
|
return nil, attribution.ErrBucketNotAttributed.New("%q", bucketName)
|
2019-06-21 20:14:34 +01:00
|
|
|
}
|
2019-06-19 13:02:37 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return attributionFromDBX(dbxInfo)
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Insert implements create partner info.
|
2019-06-19 13:02:37 +01:00
|
|
|
func (keys *attributionDB) Insert(ctx context.Context, info *attribution.Info) (_ *attribution.Info, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2021-04-01 15:50:53 +01:00
|
|
|
err = keys.db.QueryRowContext(ctx, `
|
2021-09-23 00:38:18 +01:00
|
|
|
INSERT INTO value_attributions (project_id, bucket_name, partner_id, user_agent, last_updated)
|
|
|
|
VALUES ($1, $2, $3, $4, now())
|
2021-04-01 15:50:53 +01:00
|
|
|
ON CONFLICT (project_id, bucket_name) DO NOTHING
|
|
|
|
RETURNING last_updated
|
2021-09-23 00:38:18 +01:00
|
|
|
`, info.ProjectID[:], info.BucketName, info.PartnerID[:], info.UserAgent).Scan(&info.CreatedAt)
|
2021-04-01 15:50:53 +01:00
|
|
|
// TODO when sql.ErrNoRows is returned then CreatedAt is not set
|
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
|
|
return info, nil
|
|
|
|
}
|
2019-06-19 13:02:37 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2021-04-01 15:50:53 +01:00
|
|
|
return info, nil
|
2019-06-19 13:02:37 +01:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// QueryAttribution queries partner bucket attribution data.
|
2021-09-23 00:38:18 +01:00
|
|
|
func (keys *attributionDB) QueryAttribution(ctx context.Context, partnerID uuid.UUID, userAgent []byte, start time.Time, end time.Time) (_ []*attribution.CSVRow, err error) {
|
2019-06-25 21:58:38 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2021-09-23 00:38:18 +01:00
|
|
|
rows, err := keys.db.DB.QueryContext(ctx, keys.db.Rebind(valueAttrQuery), partnerID[:], userAgent, start.UTC(), end.UTC(), partnerID[:], userAgent, start.UTC(), end.UTC())
|
2019-06-25 21:58:38 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
defer func() { err = errs.Combine(err, rows.Close()) }()
|
2020-01-16 14:27:24 +00:00
|
|
|
|
2019-08-22 12:40:15 +01:00
|
|
|
results := []*attribution.CSVRow{}
|
2019-06-25 21:58:38 +01:00
|
|
|
for rows.Next() {
|
|
|
|
r := &attribution.CSVRow{}
|
2021-07-01 12:29:25 +01:00
|
|
|
var inline, remote float64
|
2021-09-23 00:38:18 +01:00
|
|
|
err := rows.Scan(&r.PartnerID, &r.UserAgent, &r.ProjectID, &r.BucketName, &r.TotalBytesPerHour, &inline, &remote, &r.EgressData)
|
2019-06-25 21:58:38 +01:00
|
|
|
if err != nil {
|
|
|
|
return results, Error.Wrap(err)
|
|
|
|
}
|
2021-07-01 12:29:25 +01:00
|
|
|
|
|
|
|
if r.TotalBytesPerHour == 0 {
|
|
|
|
r.TotalBytesPerHour = inline + remote
|
|
|
|
}
|
|
|
|
|
2019-06-25 21:58:38 +01:00
|
|
|
results = append(results, r)
|
|
|
|
}
|
2020-01-19 17:57:42 +00:00
|
|
|
return results, Error.Wrap(rows.Err())
|
2019-06-25 21:58:38 +01:00
|
|
|
}
|
|
|
|
|
2019-06-19 13:02:37 +01:00
|
|
|
func attributionFromDBX(info *dbx.ValueAttribution) (*attribution.Info, error) {
|
2020-03-31 17:49:16 +01:00
|
|
|
partnerID, err := uuid.FromBytes(info.PartnerId)
|
2019-06-19 13:02:37 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
2021-09-23 00:38:18 +01:00
|
|
|
userAgent := info.UserAgent
|
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
2020-03-31 17:49:16 +01:00
|
|
|
projectID, err := uuid.FromBytes(info.ProjectId)
|
2019-06-19 13:02:37 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &attribution.Info{
|
|
|
|
ProjectID: projectID,
|
|
|
|
BucketName: info.BucketName,
|
|
|
|
PartnerID: partnerID,
|
2021-09-23 00:38:18 +01:00
|
|
|
UserAgent: userAgent,
|
2019-06-19 13:02:37 +01:00
|
|
|
CreatedAt: info.LastUpdated,
|
|
|
|
}, nil
|
|
|
|
}
|