2019-03-18 10:55:06 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package storagenodedb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
2019-07-09 01:33:50 +01:00
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
2019-04-08 17:46:38 +01:00
|
|
|
"time"
|
2019-03-18 10:55:06 +00:00
|
|
|
|
|
|
|
"github.com/gogo/protobuf/proto"
|
2019-04-08 17:46:38 +01:00
|
|
|
"github.com/zeebo/errs"
|
2019-03-18 10:55:06 +00:00
|
|
|
|
|
|
|
"storj.io/storj/pkg/pb"
|
|
|
|
"storj.io/storj/pkg/storj"
|
|
|
|
"storj.io/storj/storagenode/pieces"
|
|
|
|
)
|
|
|
|
|
2019-07-09 01:33:50 +01:00
|
|
|
type pieceinfo struct {
|
|
|
|
*InfoDB
|
|
|
|
space spaceUsed
|
|
|
|
}
|
|
|
|
|
|
|
|
type spaceUsed struct {
|
2019-07-10 18:47:22 +01:00
|
|
|
// Moved to top of struct to resolve alignment issue with atomic operations on ARM
|
2019-07-09 01:33:50 +01:00
|
|
|
used int64
|
2019-07-10 18:47:22 +01:00
|
|
|
once sync.Once
|
2019-07-09 01:33:50 +01:00
|
|
|
}
|
2019-03-18 10:55:06 +00:00
|
|
|
|
|
|
|
// PieceInfo returns database for storing piece information
|
|
|
|
func (db *DB) PieceInfo() pieces.DB { return db.info.PieceInfo() }
|
|
|
|
|
|
|
|
// PieceInfo returns database for storing piece information
|
2019-07-09 01:33:50 +01:00
|
|
|
func (db *InfoDB) PieceInfo() pieces.DB { return &db.pieceinfo }
|
2019-03-18 10:55:06 +00:00
|
|
|
|
|
|
|
// Add inserts piece information into the database.
|
2019-06-04 13:31:39 +01:00
|
|
|
func (db *pieceinfo) Add(ctx context.Context, info *pieces.Info) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-07-09 01:33:50 +01:00
|
|
|
|
2019-07-11 21:51:40 +01:00
|
|
|
orderLimit, err := proto.Marshal(info.OrderLimit)
|
|
|
|
if err != nil {
|
|
|
|
return ErrInfo.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2019-03-18 10:55:06 +00:00
|
|
|
uplinkPieceHash, err := proto.Marshal(info.UplinkPieceHash)
|
|
|
|
if err != nil {
|
|
|
|
return ErrInfo.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2019-07-15 18:38:08 +01:00
|
|
|
var pieceExpiration *time.Time
|
|
|
|
if !info.PieceExpiration.IsZero() {
|
|
|
|
utcExpiration := info.PieceExpiration.UTC()
|
|
|
|
pieceExpiration = &utcExpiration
|
|
|
|
}
|
|
|
|
|
2019-07-09 22:33:45 +01:00
|
|
|
// TODO remove `uplink_cert_id` from DB
|
2019-05-08 12:11:59 +01:00
|
|
|
_, err = db.db.ExecContext(ctx, db.Rebind(`
|
2019-03-18 10:55:06 +00:00
|
|
|
INSERT INTO
|
2019-07-11 21:51:40 +01:00
|
|
|
pieceinfo(satellite_id, piece_id, piece_size, piece_creation, piece_expiration, order_limit, uplink_piece_hash, uplink_cert_id)
|
|
|
|
VALUES (?,?,?,?,?,?,?,?)
|
2019-07-15 18:38:08 +01:00
|
|
|
`), info.SatelliteID, info.PieceID, info.PieceSize, info.PieceCreation.UTC(), pieceExpiration, orderLimit, uplinkPieceHash, 0)
|
2019-03-18 10:55:06 +00:00
|
|
|
|
2019-07-09 01:33:50 +01:00
|
|
|
if err == nil {
|
|
|
|
db.loadSpaceUsed(ctx)
|
|
|
|
atomic.AddInt64(&db.space.used, info.PieceSize)
|
|
|
|
}
|
2019-03-18 10:55:06 +00:00
|
|
|
return ErrInfo.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2019-07-10 14:41:47 +01:00
|
|
|
// GetPieceIDs gets pieceIDs using the satelliteID
|
|
|
|
func (db *pieceinfo) GetPieceIDs(ctx context.Context, satelliteID storj.NodeID, createdBefore time.Time, limit, offset int) (pieceIDs []storj.PieceID, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
rows, err := db.db.QueryContext(ctx, db.Rebind(`
|
|
|
|
SELECT piece_id
|
|
|
|
FROM pieceinfo
|
2019-07-11 21:04:22 +01:00
|
|
|
WHERE satellite_id = ? AND datetime(piece_creation) < datetime(?)
|
2019-07-10 14:41:47 +01:00
|
|
|
ORDER BY piece_id
|
|
|
|
LIMIT ? OFFSET ?
|
2019-07-15 18:38:08 +01:00
|
|
|
`), satelliteID, createdBefore.UTC(), limit, offset)
|
2019-07-10 14:41:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, ErrInfo.Wrap(err)
|
|
|
|
}
|
|
|
|
defer func() { err = errs.Combine(err, rows.Close()) }()
|
|
|
|
for rows.Next() {
|
|
|
|
var pieceID storj.PieceID
|
|
|
|
err = rows.Scan(&pieceID)
|
|
|
|
if err != nil {
|
|
|
|
return pieceIDs, ErrInfo.Wrap(err)
|
|
|
|
}
|
|
|
|
pieceIDs = append(pieceIDs, pieceID)
|
|
|
|
}
|
|
|
|
return pieceIDs, nil
|
|
|
|
}
|
|
|
|
|
2019-03-18 10:55:06 +00:00
|
|
|
// Get gets piece information by satellite id and piece id.
|
2019-06-04 13:31:39 +01:00
|
|
|
func (db *pieceinfo) Get(ctx context.Context, satelliteID storj.NodeID, pieceID storj.PieceID) (_ *pieces.Info, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-03-18 10:55:06 +00:00
|
|
|
info := &pieces.Info{}
|
|
|
|
info.SatelliteID = satelliteID
|
|
|
|
info.PieceID = pieceID
|
|
|
|
|
2019-07-11 21:51:40 +01:00
|
|
|
var orderLimit []byte
|
2019-03-18 10:55:06 +00:00
|
|
|
var uplinkPieceHash []byte
|
|
|
|
|
2019-06-04 13:31:39 +01:00
|
|
|
err = db.db.QueryRowContext(ctx, db.Rebind(`
|
2019-07-11 21:51:40 +01:00
|
|
|
SELECT piece_size, piece_creation, piece_expiration, order_limit, uplink_piece_hash
|
2019-03-18 10:55:06 +00:00
|
|
|
FROM pieceinfo
|
|
|
|
WHERE satellite_id = ? AND piece_id = ?
|
2019-07-11 21:51:40 +01:00
|
|
|
`), satelliteID, pieceID).Scan(&info.PieceSize, &info.PieceCreation, &info.PieceExpiration, &orderLimit, &uplinkPieceHash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrInfo.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
info.OrderLimit = &pb.OrderLimit{}
|
|
|
|
err = proto.Unmarshal(orderLimit, info.OrderLimit)
|
2019-03-18 10:55:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, ErrInfo.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
info.UplinkPieceHash = &pb.PieceHash{}
|
|
|
|
err = proto.Unmarshal(uplinkPieceHash, info.UplinkPieceHash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrInfo.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete deletes piece information.
|
2019-06-04 13:31:39 +01:00
|
|
|
func (db *pieceinfo) Delete(ctx context.Context, satelliteID storj.NodeID, pieceID storj.PieceID) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-03-18 10:55:06 +00:00
|
|
|
|
2019-07-09 01:33:50 +01:00
|
|
|
var pieceSize int64
|
|
|
|
err = db.db.QueryRowContext(ctx, db.Rebind(`
|
|
|
|
SELECT piece_size
|
|
|
|
FROM pieceinfo
|
|
|
|
WHERE satellite_id = ? AND piece_id = ?
|
|
|
|
`), satelliteID, pieceID).Scan(&pieceSize)
|
|
|
|
// Ignore no rows found errors
|
|
|
|
if err != nil && err != sql.ErrNoRows {
|
|
|
|
return ErrInfo.Wrap(err)
|
|
|
|
}
|
2019-06-04 13:31:39 +01:00
|
|
|
_, err = db.db.ExecContext(ctx, db.Rebind(`
|
|
|
|
DELETE FROM pieceinfo
|
|
|
|
WHERE satellite_id = ?
|
2019-05-08 12:11:59 +01:00
|
|
|
AND piece_id = ?
|
|
|
|
`), satelliteID, pieceID)
|
2019-03-18 10:55:06 +00:00
|
|
|
|
2019-07-09 01:33:50 +01:00
|
|
|
if pieceSize != 0 && err == nil {
|
|
|
|
db.loadSpaceUsed(ctx)
|
|
|
|
|
|
|
|
atomic.AddInt64(&db.space.used, -pieceSize)
|
|
|
|
}
|
|
|
|
|
2019-03-18 10:55:06 +00:00
|
|
|
return ErrInfo.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2019-05-08 12:11:59 +01:00
|
|
|
// DeleteFailed marks piece as a failed deletion.
|
2019-06-04 13:31:39 +01:00
|
|
|
func (db *pieceinfo) DeleteFailed(ctx context.Context, satelliteID storj.NodeID, pieceID storj.PieceID, now time.Time) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-03-18 10:55:06 +00:00
|
|
|
|
2019-06-04 13:31:39 +01:00
|
|
|
_, err = db.db.ExecContext(ctx, db.Rebind(`
|
|
|
|
UPDATE pieceinfo
|
2019-05-08 12:11:59 +01:00
|
|
|
SET deletion_failed_at = ?
|
2019-06-04 13:31:39 +01:00
|
|
|
WHERE satellite_id = ?
|
2019-05-08 12:11:59 +01:00
|
|
|
AND piece_id = ?
|
2019-07-15 18:38:08 +01:00
|
|
|
`), now.UTC(), satelliteID, pieceID)
|
2019-05-08 12:11:59 +01:00
|
|
|
|
|
|
|
return ErrInfo.Wrap(err)
|
2019-03-18 10:55:06 +00:00
|
|
|
}
|
2019-04-08 17:46:38 +01:00
|
|
|
|
2019-05-08 12:11:59 +01:00
|
|
|
// GetExpired gets pieceinformation identites that are expired.
|
|
|
|
func (db *pieceinfo) GetExpired(ctx context.Context, expiredAt time.Time, limit int64) (infos []pieces.ExpiredInfo, err error) {
|
2019-06-04 13:31:39 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-04-08 17:46:38 +01:00
|
|
|
|
2019-05-08 12:11:59 +01:00
|
|
|
rows, err := db.db.QueryContext(ctx, db.Rebind(`
|
|
|
|
SELECT satellite_id, piece_id, piece_size
|
|
|
|
FROM pieceinfo
|
2019-07-12 20:29:09 +01:00
|
|
|
WHERE piece_expiration IS NOT NULL
|
2019-07-15 18:38:08 +01:00
|
|
|
AND piece_expiration < ?
|
|
|
|
AND ((deletion_failed_at IS NULL) OR deletion_failed_at <> ?)
|
2019-05-08 12:11:59 +01:00
|
|
|
ORDER BY satellite_id
|
|
|
|
LIMIT ?
|
2019-07-15 18:38:08 +01:00
|
|
|
`), expiredAt.UTC(), expiredAt.UTC(), limit)
|
2019-04-08 17:46:38 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, ErrInfo.Wrap(err)
|
|
|
|
}
|
|
|
|
defer func() { err = errs.Combine(err, rows.Close()) }()
|
|
|
|
for rows.Next() {
|
2019-05-08 12:11:59 +01:00
|
|
|
info := pieces.ExpiredInfo{}
|
|
|
|
err = rows.Scan(&info.SatelliteID, &info.PieceID, &info.PieceSize)
|
2019-04-08 17:46:38 +01:00
|
|
|
if err != nil {
|
2019-05-08 12:11:59 +01:00
|
|
|
return infos, ErrInfo.Wrap(err)
|
2019-04-08 17:46:38 +01:00
|
|
|
}
|
2019-05-08 12:11:59 +01:00
|
|
|
infos = append(infos, info)
|
2019-04-08 17:46:38 +01:00
|
|
|
}
|
2019-05-08 12:11:59 +01:00
|
|
|
return infos, nil
|
2019-04-08 17:46:38 +01:00
|
|
|
}
|
|
|
|
|
2019-07-09 01:33:50 +01:00
|
|
|
// SpaceUsed returns disk space used by all pieces from cache
|
2019-06-04 13:31:39 +01:00
|
|
|
func (db *pieceinfo) SpaceUsed(ctx context.Context) (_ int64, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-07-09 01:33:50 +01:00
|
|
|
db.loadSpaceUsed(ctx)
|
2019-04-08 17:46:38 +01:00
|
|
|
|
2019-07-09 01:33:50 +01:00
|
|
|
return atomic.LoadInt64(&db.space.used), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *pieceinfo) loadSpaceUsed(ctx context.Context) {
|
2019-07-13 16:04:54 +01:00
|
|
|
defer mon.Task()(&ctx)(nil)
|
2019-07-09 01:33:50 +01:00
|
|
|
db.space.once.Do(func() {
|
|
|
|
usedSpace, _ := db.CalculatedSpaceUsed(ctx)
|
|
|
|
atomic.AddInt64(&db.space.used, usedSpace)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// CalculatedSpaceUsed calculates disk space used by all pieces
|
|
|
|
func (db *pieceinfo) CalculatedSpaceUsed(ctx context.Context) (_ int64, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-06-04 13:31:39 +01:00
|
|
|
var sum sql.NullInt64
|
|
|
|
err = db.db.QueryRowContext(ctx, db.Rebind(`
|
2019-05-08 12:11:59 +01:00
|
|
|
SELECT SUM(piece_size)
|
|
|
|
FROM pieceinfo
|
|
|
|
`)).Scan(&sum)
|
2019-04-08 17:46:38 +01:00
|
|
|
|
2019-06-04 13:31:39 +01:00
|
|
|
if err == sql.ErrNoRows || !sum.Valid {
|
2019-05-08 12:11:59 +01:00
|
|
|
return 0, nil
|
|
|
|
}
|
2019-06-04 13:31:39 +01:00
|
|
|
return sum.Int64, err
|
2019-04-08 17:46:38 +01:00
|
|
|
}
|
2019-06-20 12:52:32 +01:00
|
|
|
|
|
|
|
// SpaceUsed calculates disk space used by all pieces
|
|
|
|
func (db *pieceinfo) SpaceUsedBySatellite(ctx context.Context, satelliteID storj.NodeID) (_ int64, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
var sum sql.NullInt64
|
|
|
|
err = db.db.QueryRowContext(ctx, db.Rebind(`
|
|
|
|
SELECT SUM(piece_size)
|
|
|
|
FROM pieceinfo
|
|
|
|
WHERE satellite_id = ?
|
|
|
|
`), satelliteID).Scan(&sum)
|
|
|
|
|
|
|
|
if err == sql.ErrNoRows || !sum.Valid {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
return sum.Int64, err
|
|
|
|
}
|