diff --git a/lib/kv/bolt.go b/lib/kv/bolt.go index 39cf2ed15..4b672350d 100644 --- a/lib/kv/bolt.go +++ b/lib/kv/bolt.go @@ -33,6 +33,7 @@ type DB struct { refs int bolt *bbolt.DB mu sync.Mutex + readOnly bool canWrite bool queue chan *request lockTime time.Duration @@ -138,6 +139,10 @@ func (db *DB) open(ctx context.Context, forWrite bool) (err error) { } _ = db.close() + if db.readOnly && forWrite { + return ErrReadOnly + } + db.canWrite = forWrite if !forWrite { // mitigate https://github.com/etcd-io/bbolt/issues/98 @@ -228,6 +233,11 @@ func (db *DB) Do(write bool, op Op) error { return r.err } +// ReadOnly setter the db.readOnly +func (db *DB) ReadOnly(b bool) { + db.readOnly = b +} + // request encapsulates a synchronous operation and its results type request struct { op Op diff --git a/lib/kv/internal_test.go b/lib/kv/internal_test.go index 5ff47b1a6..f018f2722 100644 --- a/lib/kv/internal_test.go +++ b/lib/kv/internal_test.go @@ -66,3 +66,27 @@ func TestKvExit(t *testing.T) { Exit() assert.Equal(t, 0, len(dbMap)) } + +func TestDbReadOnly(t *testing.T) { + require.Equal(t, 0, len(dbMap), "no databases can be started initially") + ctx := context.Background() + + db, err := Start(ctx, "test", nil) + require.NoError(t, err) + require.NotNil(t, db) + + assert.False(t, db.readOnly) + + // set db in read only + db.ReadOnly(true) + + assert.True(t, db.readOnly) + + // write op is not allowed in read only mode, should throw error + err = db.Do(true, nil) + assert.ErrorIs(t, err, ErrReadOnly) + + db.ReadOnly(false) + err = db.Do(true, &opStop{}) + assert.NoError(t, err) +} diff --git a/lib/kv/types.go b/lib/kv/types.go index 601dd21da..bd3081f45 100644 --- a/lib/kv/types.go +++ b/lib/kv/types.go @@ -10,6 +10,7 @@ var ( ErrEmpty = errors.New("database empty") ErrInactive = errors.New("database stopped") ErrUnsupported = errors.New("unsupported on this OS") + ErrReadOnly = errors.New("database in read-only mode") ) // Op represents a database operation