Skip to content

Commit

Permalink
storage.engine: add version file to store directory
Browse files Browse the repository at this point in the history
This adds a new file COCKROACHDB_VERSION to all store directories. When needed,
having this file will allow us to automatically perform migrations during the
call to rocksDB.Open().

Fixes #2718.
  • Loading branch information
BramGruneir committed Mar 30, 2016
1 parent 349c4ca commit df6761f
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 2 deletions.
24 changes: 22 additions & 2 deletions storage/engine/rocksdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,41 @@ func (r *RocksDB) Open() error {
humanize.IBytes(minMemtableBudget), util.IBytes(r.memtableBudget))
}

var ver int
if len(r.dir) != 0 {
log.Infof("opening rocksdb instance at %q", r.dir)

// Check the version number.
var err error
if ver, err = getVersion(r.dir); err != nil {
return err
}
if ver < versionMinimum || ver > versionCurrent {
// Instead of an error, we should call a migration if possible when
// one is needed immediately following the DBOpen call.
return fmt.Errorf("incompatible rocksdb data version, current:%d, on disk:%d, minimum:%d",
versionCurrent, ver, versionMinimum)
}
}

status := C.DBOpen(&r.rdb, goToCSlice([]byte(r.dir)),
C.DBOptions{
cache_size: C.uint64_t(r.cacheSize),
memtable_budget: C.uint64_t(r.memtableBudget),
allow_os_buffer: C.bool(true),
logging_enabled: C.bool(log.V(3)),
})
err := statusToError(status)
if err != nil {
if err := statusToError(status); err != nil {
return util.Errorf("could not open rocksdb instance: %s", err)
}

// Write the version file if none exists.
if len(r.dir) > 0 && ver < versionCurrent {
if err := writeVersionFile(r.dir); err != nil {
return err
}
}

// Start a goroutine that will finish when the underlying handle
// is deallocated. This is used to check a leak in tests.
go func() {
Expand Down
63 changes: 63 additions & 0 deletions storage/engine/rocksdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
package engine

import (
"encoding/json"
"io/ioutil"
"math/rand"
"os"
"strconv"
"testing"

Expand Down Expand Up @@ -139,3 +142,63 @@ func benchmarkIterOnBatch(b *testing.B, writes int) {
iter.Close()
}
}

// TestRocksDBOpenWithVersions verifies the version checking in Open()
// functions correctly.
func TestRocksDBOpenWithVersions(t *testing.T) {
defer leaktest.AfterTest(t)()

testCases := []struct {
hasFile bool
ver Version
expectedErr string
}{
{false, Version{}, ""},
{true, Version{versionCurrent}, ""},
{true, Version{versionMinimum}, ""},
{true, Version{-1}, "incompatible rocksdb data version, current:1, on disk:-1, minimum:0"},
{true, Version{2}, "incompatible rocksdb data version, current:1, on disk:2, minimum:0"},
}

for i, testCase := range testCases {
err := openRocksDBWithVersion(t, testCase.hasFile, testCase.ver)
if err == nil && len(testCase.expectedErr) == 0 {
continue
}
if !testutils.IsError(err, testCase.expectedErr) {
t.Errorf("%d: expected error '%s', actual '%s'", i, testCase.expectedErr, err)
}
}
}

// openRocksDBWithVersion attempts to open a rocks db instance, optionally with
// the supplied Version struct. It then closes the
func openRocksDBWithVersion(t *testing.T, hasVersionFile bool, ver Version) error {
stopper := stop.NewStopper()
defer stopper.Stop()

dir, err := ioutil.TempDir("", "testing")
if err != nil {
t.Fatal(err)
}
defer func() {
if err := os.RemoveAll(dir); err != nil {
t.Fatal(err)
}
}()

if hasVersionFile {
b, err := json.Marshal(ver)
if err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(getVersionFilename(dir), b, 0644); err != nil {
t.Fatal(err)
}
}

rocksdb := NewRocksDB(roachpb.Attributes{}, dir, 512<<20, minMemtableBudget, 0, stopper)
err = rocksdb.Open()
defer rocksdb.Close()
return err
}
77 changes: 77 additions & 0 deletions storage/engine/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2016 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

package engine

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)

const (
versionFilename = "COCKROACHDB_VERSION"
versionNoFile = 0
versionCurrent = 1
versionMinimum = 0
)

// Version stores all the version information for all stores and is used as
// the format for the version file.
type Version struct {
Version int `json:"Version"`
}

// getVersionFilename returns the filename for the version file stored in the
// data directory.
func getVersionFilename(dir string) string {
return filepath.Join(dir, versionFilename)
}

// getVersions returns the current on disk cockroach version from the version
// file from the passed in directory. If there is no version file yet, it
// returns 0.
func getVersion(dir string) (int, error) {
filename := getVersionFilename(dir)
b, err := ioutil.ReadFile(filename)
if err != nil {
if os.IsNotExist(err) {
return 0, nil
}
return 0, err
}
var ver Version
if err := json.Unmarshal(b, &ver); err != nil {
return 0, fmt.Errorf("version file %s is not formatted correctly; %s", filename, err)
}
return ver.Version, nil
}

// writeVersionFile overwrites the version file to contain the latest version.
func writeVersionFile(dir string) error {
filename := getVersionFilename(dir)
if len(filename) == 0 {
return nil
}
if err := os.Remove(filename); err != nil && !os.IsNotExist(err) {
return err
}
b, err := json.Marshal(Version{versionCurrent})
if err != nil {
return err
}
return ioutil.WriteFile(filename, b, 0644)
}
74 changes: 74 additions & 0 deletions storage/engine/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2016 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

package engine

import (
"io/ioutil"
"os"
"testing"

"github.com/cockroachdb/cockroach/testutils"
"github.com/cockroachdb/cockroach/util/leaktest"
)

// TestVersions verifies that both getVersions() and writeVersionFile work
// correctly.
func TestVersions(t *testing.T) {
defer leaktest.AfterTest(t)()

dir, err := ioutil.TempDir("", "testing")
if err != nil {
t.Fatal(err)
}
defer func() {
if err := os.RemoveAll(dir); err != nil {
t.Fatal(err)
}
}()

// First test when no file exists yet.
ver, err := getVersion(dir)
if err != nil {
t.Fatal(err)
}
if ver != versionNoFile {
t.Errorf("no version file version should be %d, got %d", versionNoFile, ver)
}

// Write the current versions to the file.
if err := writeVersionFile(dir); err != nil {
t.Fatal(err)
}
ver, err = getVersion(dir)
if err != nil {
t.Fatal(err)
}
if ver != versionCurrent {
t.Errorf("current versions do not match, expected %d got %d", versionCurrent, ver)
}

// Write gibberish to the file.
filename := getVersionFilename(dir)
if err := os.Remove(filename); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filename, []byte("cause an error please"), 0644); err != nil {
t.Fatal(err)
}
if _, err = getVersion(dir); !testutils.IsError(err,
"is not formatted correctly") {
t.Errorf("expected error contains '%s', got '%s'", "is not formatted correctly", err)
}
}

0 comments on commit df6761f

Please sign in to comment.