2019-04-02 15:55:58 +01:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package inspector
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-12-16 15:25:27 +00:00
|
|
|
"encoding/binary"
|
2019-04-02 15:55:58 +01:00
|
|
|
|
2019-11-08 20:40:39 +00:00
|
|
|
"github.com/spacemonkeygo/monkit/v3"
|
2019-04-02 15:55:58 +01:00
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
2019-12-27 11:48:47 +00:00
|
|
|
"storj.io/common/pb"
|
|
|
|
"storj.io/common/storj"
|
2020-03-30 10:08:50 +01:00
|
|
|
"storj.io/common/uuid"
|
2020-10-30 11:12:01 +00:00
|
|
|
"storj.io/storj/satellite/internalpb"
|
2019-04-02 15:55:58 +01:00
|
|
|
"storj.io/storj/satellite/metainfo"
|
2020-12-14 10:11:28 +00:00
|
|
|
"storj.io/storj/satellite/metainfo/metabase"
|
2019-07-28 06:55:36 +01:00
|
|
|
"storj.io/storj/satellite/overlay"
|
2019-04-02 15:55:58 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
mon = monkit.Package()
|
2020-08-11 15:50:01 +01:00
|
|
|
// Error wraps errors returned from Server struct methods.
|
2019-04-02 15:55:58 +01:00
|
|
|
Error = errs.Class("Endpoint error")
|
|
|
|
)
|
|
|
|
|
2020-12-05 16:01:42 +00:00
|
|
|
// Endpoint for checking object and segment health.
|
2019-09-10 14:24:16 +01:00
|
|
|
//
|
|
|
|
// architecture: Endpoint
|
2019-04-02 15:55:58 +01:00
|
|
|
type Endpoint struct {
|
2020-12-16 15:25:27 +00:00
|
|
|
log *zap.Logger
|
|
|
|
overlay *overlay.Service
|
|
|
|
metabaseDB metainfo.MetabaseDB
|
2019-04-02 15:55:58 +01:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// NewEndpoint will initialize an Endpoint struct.
|
2020-12-16 15:25:27 +00:00
|
|
|
func NewEndpoint(log *zap.Logger, cache *overlay.Service, metabaseDB metainfo.MetabaseDB) *Endpoint {
|
2019-04-02 15:55:58 +01:00
|
|
|
return &Endpoint{
|
2020-12-16 15:25:27 +00:00
|
|
|
log: log,
|
|
|
|
overlay: cache,
|
|
|
|
metabaseDB: metabaseDB,
|
2019-04-02 15:55:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// ObjectHealth will check the health of an object.
|
2020-10-30 11:12:01 +00:00
|
|
|
func (endpoint *Endpoint) ObjectHealth(ctx context.Context, in *internalpb.ObjectHealthRequest) (resp *internalpb.ObjectHealthResponse, err error) {
|
2019-04-02 15:55:58 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2020-10-30 11:12:01 +00:00
|
|
|
var segmentHealthResponses []*internalpb.SegmentHealth
|
2019-04-02 15:55:58 +01:00
|
|
|
var redundancy *pb.RedundancyScheme
|
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
limit := int(100)
|
2019-04-02 15:55:58 +01:00
|
|
|
if in.GetLimit() > 0 {
|
2020-12-16 15:25:27 +00:00
|
|
|
limit = int(in.GetLimit())
|
2019-04-02 15:55:58 +01:00
|
|
|
}
|
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
var startPosition metabase.SegmentPosition
|
|
|
|
|
2019-04-02 15:55:58 +01:00
|
|
|
if in.GetStartAfterSegment() > 0 {
|
2020-12-16 15:25:27 +00:00
|
|
|
startPosition = metabase.SegmentPositionFromEncoded(uint64(in.GetStartAfterSegment()))
|
2019-04-02 15:55:58 +01:00
|
|
|
}
|
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
projectID, err := uuid.FromBytes(in.GetProjectId())
|
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
2019-04-02 15:55:58 +01:00
|
|
|
}
|
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
objectLocation := metabase.ObjectLocation{
|
|
|
|
ProjectID: projectID,
|
|
|
|
BucketName: string(in.GetBucket()),
|
|
|
|
ObjectKey: metabase.ObjectKey(in.GetEncryptedPath()),
|
|
|
|
}
|
|
|
|
// TODO add version field to ObjectHealthRequest?
|
|
|
|
object, err := endpoint.metabaseDB.GetObjectLatestVersion(ctx, metabase.GetObjectLatestVersion{
|
|
|
|
ObjectLocation: objectLocation,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
2019-04-02 15:55:58 +01:00
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
listResult, err := endpoint.metabaseDB.ListSegments(ctx, metabase.ListSegments{
|
|
|
|
StreamID: object.StreamID,
|
|
|
|
Cursor: startPosition,
|
|
|
|
Limit: limit,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
2019-04-02 15:55:58 +01:00
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
for _, segment := range listResult.Segments {
|
|
|
|
if !segment.Inline() {
|
|
|
|
segmentHealth, err := endpoint.segmentHealth(ctx, segment)
|
|
|
|
if err != nil {
|
2019-04-02 15:55:58 +01:00
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
2020-12-16 15:25:27 +00:00
|
|
|
segmentHealthResponses = append(segmentHealthResponses, segmentHealth.GetHealth())
|
|
|
|
redundancy = segmentHealth.GetRedundancy()
|
2019-04-02 15:55:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-30 11:12:01 +00:00
|
|
|
return &internalpb.ObjectHealthResponse{
|
2019-04-02 15:55:58 +01:00
|
|
|
Segments: segmentHealthResponses,
|
|
|
|
Redundancy: redundancy,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// SegmentHealth will check the health of a segment.
|
2020-12-16 15:25:27 +00:00
|
|
|
func (endpoint *Endpoint) SegmentHealth(ctx context.Context, in *internalpb.SegmentHealthRequest) (_ *internalpb.SegmentHealthResponse, err error) {
|
2019-04-02 15:55:58 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
projectID, err := uuid.FromBytes(in.GetProjectId())
|
2019-04-02 15:55:58 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
objectLocation := metabase.ObjectLocation{
|
|
|
|
ProjectID: projectID,
|
|
|
|
BucketName: string(in.GetBucket()),
|
|
|
|
ObjectKey: metabase.ObjectKey(in.GetEncryptedPath()),
|
|
|
|
}
|
|
|
|
|
|
|
|
object, err := endpoint.metabaseDB.GetObjectLatestVersion(ctx, metabase.GetObjectLatestVersion{
|
|
|
|
ObjectLocation: objectLocation,
|
|
|
|
})
|
2019-04-02 15:55:58 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
segment, err := endpoint.metabaseDB.GetSegmentByPosition(ctx, metabase.GetSegmentByPosition{
|
|
|
|
StreamID: object.StreamID,
|
|
|
|
Position: metabase.SegmentPositionFromEncoded(uint64(in.GetSegmentIndex())),
|
|
|
|
})
|
2019-04-02 15:55:58 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
if segment.Inline() {
|
2019-04-02 15:55:58 +01:00
|
|
|
return nil, Error.New("cannot check health of inline segment")
|
|
|
|
}
|
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
return endpoint.segmentHealth(ctx, segment)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (endpoint *Endpoint) segmentHealth(ctx context.Context, segment metabase.Segment) (_ *internalpb.SegmentHealthResponse, err error) {
|
|
|
|
|
|
|
|
health := &internalpb.SegmentHealth{}
|
2019-04-02 15:55:58 +01:00
|
|
|
var nodeIDs storj.NodeIDList
|
2020-12-16 15:25:27 +00:00
|
|
|
for _, piece := range segment.Pieces {
|
|
|
|
nodeIDs = append(nodeIDs, piece.StorageNode)
|
2019-04-02 15:55:58 +01:00
|
|
|
}
|
|
|
|
|
2019-08-06 17:35:59 +01:00
|
|
|
unreliableOrOfflineNodes, err := endpoint.overlay.KnownUnreliableOrOffline(ctx, nodeIDs)
|
2019-04-02 15:55:58 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
2019-06-18 23:22:14 +01:00
|
|
|
|
2019-08-06 17:35:59 +01:00
|
|
|
offlineNodes, err := endpoint.overlay.KnownOffline(ctx, nodeIDs)
|
2019-06-18 23:22:14 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
offlineMap := make(map[storj.NodeID]bool)
|
|
|
|
for _, id := range offlineNodes {
|
|
|
|
offlineMap[id] = true
|
|
|
|
}
|
|
|
|
unreliableOfflineMap := make(map[storj.NodeID]bool)
|
|
|
|
for _, id := range unreliableOrOfflineNodes {
|
|
|
|
unreliableOfflineMap[id] = true
|
|
|
|
}
|
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
redundancy := &pb.RedundancyScheme{
|
|
|
|
MinReq: int32(segment.Redundancy.RequiredShares),
|
|
|
|
RepairThreshold: int32(segment.Redundancy.RepairShares),
|
|
|
|
SuccessThreshold: int32(segment.Redundancy.OptimalShares),
|
|
|
|
Total: int32(segment.Redundancy.TotalShares),
|
|
|
|
}
|
|
|
|
|
2019-06-18 23:22:14 +01:00
|
|
|
var healthyNodes storj.NodeIDList
|
|
|
|
var unhealthyNodes storj.NodeIDList
|
|
|
|
for _, id := range nodeIDs {
|
|
|
|
if offlineMap[id] {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if unreliableOfflineMap[id] {
|
|
|
|
unhealthyNodes = append(unhealthyNodes, id)
|
|
|
|
} else {
|
|
|
|
healthyNodes = append(healthyNodes, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
health.HealthyIds = healthyNodes
|
|
|
|
health.UnhealthyIds = unhealthyNodes
|
|
|
|
health.OfflineIds = offlineNodes
|
2019-04-02 15:55:58 +01:00
|
|
|
|
2020-12-16 15:25:27 +00:00
|
|
|
health.Segment = make([]byte, 8)
|
|
|
|
|
|
|
|
binary.LittleEndian.PutUint64(health.Segment, segment.Position.Encode())
|
2019-04-02 15:55:58 +01:00
|
|
|
|
2020-10-30 11:12:01 +00:00
|
|
|
return &internalpb.SegmentHealthResponse{
|
2019-04-02 15:55:58 +01:00
|
|
|
Health: health,
|
2020-12-16 15:25:27 +00:00
|
|
|
Redundancy: redundancy,
|
2019-04-02 15:55:58 +01:00
|
|
|
}, nil
|
|
|
|
}
|