2019-06-21 17:27:19 +01:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package audit_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2019-06-24 23:04:06 +01:00
|
|
|
"github.com/stretchr/testify/assert"
|
2019-06-21 17:27:19 +01:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.uber.org/zap"
|
2019-06-24 23:04:06 +01:00
|
|
|
|
|
|
|
"storj.io/storj/internal/memory"
|
2019-06-21 17:27:19 +01:00
|
|
|
"storj.io/storj/internal/testcontext"
|
|
|
|
"storj.io/storj/internal/testplanet"
|
2019-06-26 11:38:51 +01:00
|
|
|
"storj.io/storj/internal/testrand"
|
2019-07-05 09:36:35 +01:00
|
|
|
"storj.io/storj/pkg/encryption"
|
|
|
|
"storj.io/storj/pkg/paths"
|
2019-06-21 17:27:19 +01:00
|
|
|
"storj.io/storj/pkg/storj"
|
|
|
|
"storj.io/storj/satellite"
|
2019-07-28 06:55:36 +01:00
|
|
|
"storj.io/storj/satellite/audit"
|
|
|
|
"storj.io/storj/satellite/overlay"
|
2019-06-21 17:27:19 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// TestDisqualificationTooManyFailedAudits does the following:
|
|
|
|
// * Create a failed audit report for a storagenode
|
|
|
|
// * Record the audit report several times and check that the node isn't
|
|
|
|
// disqualified until the audit reputation reaches the cut-off value.
|
|
|
|
func TestDisqualificationTooManyFailedAudits(t *testing.T) {
|
|
|
|
var (
|
|
|
|
auditDQCutOff = 0.4
|
|
|
|
alpha0 float64 = 1
|
|
|
|
beta0 float64
|
|
|
|
)
|
|
|
|
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 1, Reconfigure: testplanet.Reconfigure{
|
|
|
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
|
|
|
config.Overlay.Node.AuditReputationAlpha0 = alpha0
|
|
|
|
config.Overlay.Node.AuditReputationBeta0 = beta0
|
|
|
|
config.Overlay.Node.AuditReputationLambda = 1
|
|
|
|
config.Overlay.Node.AuditReputationWeight = 1
|
|
|
|
config.Overlay.Node.AuditReputationDQ = auditDQCutOff
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
var (
|
2019-07-22 20:10:04 +01:00
|
|
|
satellitePeer = planet.Satellites[0]
|
|
|
|
nodeID = planet.StorageNodes[0].ID()
|
|
|
|
report = &audit.Report{
|
2019-06-21 17:27:19 +01:00
|
|
|
Fails: storj.NodeIDList{nodeID},
|
|
|
|
}
|
|
|
|
)
|
2019-09-11 23:37:01 +01:00
|
|
|
satellitePeer.Audit.Worker.Loop.Pause()
|
2019-06-21 17:27:19 +01:00
|
|
|
|
2019-07-22 20:10:04 +01:00
|
|
|
dossier, err := satellitePeer.Overlay.Service.Get(ctx, nodeID)
|
2019-06-21 17:27:19 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.Equal(t, alpha0, dossier.Reputation.AuditReputationAlpha)
|
|
|
|
require.Equal(t, beta0, dossier.Reputation.AuditReputationBeta)
|
|
|
|
|
|
|
|
prevReputation := calcReputation(dossier)
|
|
|
|
|
|
|
|
// Report the audit failure until the node gets disqualified due to many
|
2019-09-11 23:37:01 +01:00
|
|
|
// failed audits.
|
2019-06-21 17:27:19 +01:00
|
|
|
iterations := 1
|
|
|
|
for ; ; iterations++ {
|
2019-09-11 23:37:01 +01:00
|
|
|
_, err := satellitePeer.Audit.Reporter.RecordAudits(ctx, report)
|
2019-06-21 17:27:19 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2019-07-22 20:10:04 +01:00
|
|
|
dossier, err := satellitePeer.Overlay.Service.Get(ctx, nodeID)
|
2019-06-21 17:27:19 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
reputation := calcReputation(dossier)
|
|
|
|
require.Truef(t, prevReputation >= reputation,
|
|
|
|
"(%d) expected reputation to remain or decrease (previous >= current): %f >= %f",
|
|
|
|
iterations, prevReputation, reputation,
|
|
|
|
)
|
|
|
|
|
|
|
|
if reputation <= auditDQCutOff || reputation == prevReputation {
|
|
|
|
require.NotNilf(t, dossier.Disqualified,
|
|
|
|
"Disqualified (%d) - cut-off: %f, prev. reputation: %f, current reputation: %f",
|
|
|
|
iterations, auditDQCutOff, prevReputation, reputation,
|
|
|
|
)
|
|
|
|
|
2019-08-22 12:40:15 +01:00
|
|
|
require.True(t, time.Since(*dossier.Disqualified) >= 0,
|
2019-06-21 17:27:19 +01:00
|
|
|
"Disqualified should be in the past",
|
|
|
|
)
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
require.Nil(t, dossier.Disqualified, "Disqualified")
|
|
|
|
prevReputation = reputation
|
|
|
|
}
|
|
|
|
|
|
|
|
require.True(t, iterations > 1, "the number of iterations must be at least 2")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func calcReputation(dossier *overlay.NodeDossier) float64 {
|
|
|
|
var (
|
|
|
|
alpha = dossier.Reputation.AuditReputationAlpha
|
|
|
|
beta = dossier.Reputation.AuditReputationBeta
|
|
|
|
)
|
|
|
|
|
|
|
|
return alpha / (alpha + beta)
|
|
|
|
}
|
2019-06-24 23:04:06 +01:00
|
|
|
|
|
|
|
func TestDisqualifiedNodesGetNoDownload(t *testing.T) {
|
2019-06-26 11:38:51 +01:00
|
|
|
// Uploads random data.
|
|
|
|
// Mark a node as disqualified.
|
|
|
|
// Check we don't get it when we require order limit.
|
2019-06-24 23:04:06 +01:00
|
|
|
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 4, UplinkCount: 1,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
2019-07-22 20:10:04 +01:00
|
|
|
satellitePeer := planet.Satellites[0]
|
|
|
|
uplinkPeer := planet.Uplinks[0]
|
2019-09-11 23:37:01 +01:00
|
|
|
satellitePeer.Audit.Worker.Loop.Pause()
|
2019-06-24 23:04:06 +01:00
|
|
|
|
2019-06-26 11:38:51 +01:00
|
|
|
testData := testrand.Bytes(8 * memory.KiB)
|
2019-06-24 23:04:06 +01:00
|
|
|
|
2019-09-11 23:37:01 +01:00
|
|
|
err := uplinkPeer.Upload(ctx, satellitePeer, "testbucket", "test/path", testData)
|
2019-06-24 23:04:06 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2019-07-22 20:10:04 +01:00
|
|
|
projects, err := satellitePeer.DB.Console().Projects().GetAll(ctx)
|
2019-06-24 23:04:06 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, projects, 1)
|
|
|
|
|
|
|
|
bucketID := []byte(storj.JoinPaths(projects[0].ID.String(), "testbucket"))
|
|
|
|
|
2019-07-22 20:10:04 +01:00
|
|
|
encParameters := uplinkPeer.GetConfig(satellitePeer).GetEncryptionParameters()
|
2019-07-03 19:07:44 +01:00
|
|
|
cipherSuite := encParameters.CipherSuite
|
2019-07-05 09:36:35 +01:00
|
|
|
store := encryption.NewStore()
|
|
|
|
store.SetDefaultKey(new(storj.Key))
|
|
|
|
encryptedPath, err := encryption.EncryptPath("testbucket", paths.NewUnencrypted("test/path"), cipherSuite, store)
|
2019-06-24 23:04:06 +01:00
|
|
|
require.NoError(t, err)
|
2019-07-05 09:36:35 +01:00
|
|
|
lastSegPath := storj.JoinPaths(projects[0].ID.String(), "l", "testbucket", encryptedPath.Raw())
|
2019-07-22 20:10:04 +01:00
|
|
|
pointer, err := satellitePeer.Metainfo.Service.Get(ctx, lastSegPath)
|
2019-06-24 23:04:06 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
disqualifiedNode := pointer.GetRemote().GetRemotePieces()[0].NodeId
|
2019-07-22 20:10:04 +01:00
|
|
|
disqualifyNode(t, ctx, satellitePeer, disqualifiedNode)
|
2019-06-24 23:04:06 +01:00
|
|
|
|
2019-07-22 20:10:04 +01:00
|
|
|
limits, _, err := satellitePeer.Orders.Service.CreateGetOrderLimits(ctx, bucketID, pointer)
|
2019-06-24 23:04:06 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, limits, len(pointer.GetRemote().GetRemotePieces())-1)
|
|
|
|
|
|
|
|
for _, orderLimit := range limits {
|
2019-07-22 20:10:04 +01:00
|
|
|
assert.False(t, isDisqualified(t, ctx, satellitePeer, orderLimit.Limit.StorageNodeId))
|
2019-06-24 23:04:06 +01:00
|
|
|
assert.NotEqual(t, orderLimit.Limit.StorageNodeId, disqualifiedNode)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDisqualifiedNodesGetNoUpload(t *testing.T) {
|
|
|
|
|
|
|
|
// - mark a node as disqualified
|
|
|
|
// - check that we have an error if we try to create a segment using all storage nodes
|
|
|
|
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 4, UplinkCount: 1,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
2019-07-22 20:10:04 +01:00
|
|
|
satellitePeer := planet.Satellites[0]
|
2019-06-24 23:04:06 +01:00
|
|
|
disqualifiedNode := planet.StorageNodes[0]
|
2019-09-11 23:37:01 +01:00
|
|
|
satellitePeer.Audit.Worker.Loop.Pause()
|
2019-06-24 23:04:06 +01:00
|
|
|
|
2019-07-22 20:10:04 +01:00
|
|
|
disqualifyNode(t, ctx, satellitePeer, disqualifiedNode.ID())
|
2019-06-24 23:04:06 +01:00
|
|
|
|
|
|
|
request := overlay.FindStorageNodesRequest{
|
|
|
|
MinimumRequiredNodes: 4,
|
|
|
|
RequestedCount: 0,
|
|
|
|
FreeBandwidth: 0,
|
|
|
|
FreeDisk: 0,
|
|
|
|
ExcludedNodes: nil,
|
|
|
|
MinimumVersion: "", // semver or empty
|
|
|
|
}
|
2019-07-22 20:10:04 +01:00
|
|
|
nodes, err := satellitePeer.Overlay.Service.FindStorageNodes(ctx, request)
|
2019-06-24 23:04:06 +01:00
|
|
|
assert.True(t, overlay.ErrNotEnoughNodes.Has(err))
|
|
|
|
|
|
|
|
assert.Len(t, nodes, 3)
|
|
|
|
for _, node := range nodes {
|
2019-07-22 20:10:04 +01:00
|
|
|
assert.False(t, isDisqualified(t, ctx, satellitePeer, node.Id))
|
2019-06-24 23:04:06 +01:00
|
|
|
assert.NotEqual(t, node.Id, disqualifiedNode)
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-06-25 18:10:22 +01:00
|
|
|
func TestDisqualifiedNodeRemainsDisqualified(t *testing.T) {
|
|
|
|
|
|
|
|
// - mark a node as disqualified
|
|
|
|
// - give it high uptime and audit rate
|
|
|
|
// - check that the node remains disqualified
|
|
|
|
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 4, UplinkCount: 1,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
2019-07-22 20:10:04 +01:00
|
|
|
satellitePeer := planet.Satellites[0]
|
2019-09-11 23:37:01 +01:00
|
|
|
satellitePeer.Audit.Worker.Loop.Pause()
|
2019-06-25 18:10:22 +01:00
|
|
|
|
|
|
|
disqualifiedNode := planet.StorageNodes[0]
|
2019-07-22 20:10:04 +01:00
|
|
|
disqualifyNode(t, ctx, satellitePeer, disqualifiedNode.ID())
|
2019-06-25 18:10:22 +01:00
|
|
|
|
2019-09-11 23:37:01 +01:00
|
|
|
_, err := satellitePeer.DB.OverlayCache().UpdateUptime(ctx, disqualifiedNode.ID(), true, 0, 1, 0)
|
2019-06-25 18:10:22 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2019-07-22 20:10:04 +01:00
|
|
|
assert.True(t, isDisqualified(t, ctx, satellitePeer, disqualifiedNode.ID()))
|
2019-06-25 18:10:22 +01:00
|
|
|
|
2019-07-31 18:21:06 +01:00
|
|
|
_, err = satellitePeer.DB.OverlayCache().BatchUpdateStats(ctx, []*overlay.UpdateRequest{{
|
2019-06-25 18:10:22 +01:00
|
|
|
NodeID: disqualifiedNode.ID(),
|
|
|
|
IsUp: true,
|
|
|
|
AuditSuccess: true,
|
|
|
|
AuditLambda: 0, // forget about history
|
|
|
|
AuditWeight: 1,
|
|
|
|
AuditDQ: 0, // make sure new reputation scores are larger than the DQ thresholds
|
|
|
|
UptimeLambda: 0, // forget about history
|
|
|
|
UptimeWeight: 1,
|
|
|
|
UptimeDQ: 0, // make sure new reputation scores are larger than the DQ thresholds
|
2019-07-31 18:21:06 +01:00
|
|
|
}}, 100)
|
2019-06-25 18:10:22 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2019-07-22 20:10:04 +01:00
|
|
|
assert.True(t, isDisqualified(t, ctx, satellitePeer, disqualifiedNode.ID()))
|
2019-06-25 18:10:22 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-17 21:14:49 +01:00
|
|
|
func isDisqualified(t *testing.T, ctx *testcontext.Context, satellite *testplanet.SatelliteSystem, nodeID storj.NodeID) bool {
|
2019-06-24 23:04:06 +01:00
|
|
|
node, err := satellite.Overlay.Service.Get(ctx, nodeID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return node.Disqualified != nil
|
|
|
|
}
|
2019-09-17 21:14:49 +01:00
|
|
|
func disqualifyNode(t *testing.T, ctx *testcontext.Context, satellite *testplanet.SatelliteSystem, nodeID storj.NodeID) {
|
2019-07-31 18:21:06 +01:00
|
|
|
_, err := satellite.DB.OverlayCache().BatchUpdateStats(ctx, []*overlay.UpdateRequest{{
|
2019-06-24 23:04:06 +01:00
|
|
|
NodeID: nodeID,
|
|
|
|
IsUp: true,
|
|
|
|
AuditSuccess: false,
|
|
|
|
AuditLambda: 0,
|
|
|
|
AuditWeight: 1,
|
|
|
|
AuditDQ: 0.5,
|
|
|
|
UptimeLambda: 1,
|
|
|
|
UptimeWeight: 1,
|
|
|
|
UptimeDQ: 0.5,
|
2019-07-31 18:21:06 +01:00
|
|
|
}}, 100)
|
2019-06-24 23:04:06 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, isDisqualified(t, ctx, satellite, nodeID))
|
|
|
|
}
|