Skip to content

Commit 605dcf7

Browse files
committed
FAB-828 Create couchdb database automatically
Create couchdb database automatically for main system ledger upon startup. If couchdb database already exists do not re-create. There will be one couchdb database for system ledger, and eventually one couchdb database for each subledger. Tests for auto-create are included, as well as some minor refactoring and debug enhancements that were required as part of the testing. Change-Id: Ia90ffd4de74346e2472d31eceaf63f24ad1bf04e Signed-off-by: denyeart <[email protected]>
1 parent b4473da commit 605dcf7

File tree

6 files changed

+218
-42
lines changed

6 files changed

+218
-42
lines changed

core/ledger/kvledger/kv_ledger.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,13 @@ func NewKVLedger(conf *Conf) (*KVLedger, error) {
7777
if kvledgerconfig.IsCouchDBEnabled() == true {
7878
//By default we can talk to CouchDB with empty id and pw (""), or you can add your own id and password to talk to a secured CouchDB
7979
logger.Debugf("===COUCHDB=== NewKVLedger() Using CouchDB instead of RocksDB...hardcoding and passing connection config for now")
80+
//TODO Hardcoding and passing connection config for now, eventually this will be passed from external config
8081
txmgmt := couchdbtxmgmt.NewCouchDBTxMgr(&couchdbtxmgmt.Conf{DBPath: conf.txMgrDBPath},
81-
"127.0.0.1", //couchDB host
82-
5984, //couchDB port
83-
"marbles_app", //couchDB db name
84-
"", //enter couchDB id here
85-
"") //enter couchDB pw here
82+
"127.0.0.1", //couchDB host
83+
5984, //couchDB port
84+
"system", //couchDB db name matches ledger name, TODO for now use system ledger, eventually allow passing in subledger name
85+
"", //enter couchDB id here
86+
"") //enter couchDB pw here
8687
return &KVLedger{blockStore, txmgmt, nil}, nil
8788
}
8889

core/ledger/kvledger/kvledgerconfig/kv_ledger_config.go

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package kvledgerconfig
1818

1919
// Change this feature toggle to true to use CouchDB for state database
20+
// TODO Eventually this feature toggle will be externalized via a real
21+
// config option on the peer
2022
var useCouchDB = false
2123

2224
//IsCouchDBEnabled exposes the useCouchDB variable

core/ledger/kvledger/txmgmt/couchdbtxmgmt/couchdb/couchdb.go

+19-10
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ type DBInfo struct {
6262
InstanceStartTime string `json:"instance_start_time"`
6363
}
6464

65-
// DBConnectionDef contains parameters
66-
type DBConnectionDef struct {
65+
// CouchDBConnectionDef contains parameters
66+
type CouchDBConnectionDef struct {
6767
URL string
6868
Username string
6969
Password string
@@ -78,7 +78,7 @@ type CouchDBReturn struct {
7878
}
7979

8080
//CreateConnectionDefinition for a new client connection
81-
func CreateConnectionDefinition(host string, port int, databaseName, username, password string) (*DBConnectionDef, error) {
81+
func CreateConnectionDefinition(host string, port int, databaseName, username, password string) (*CouchDBConnectionDef, error) {
8282

8383
logger.Debugf("===COUCHDB=== Entering CreateConnectionDefinition()")
8484

@@ -94,6 +94,7 @@ func CreateConnectionDefinition(host string, port int, databaseName, username, p
9494
//parse the constructed URL to verify no errors
9595
finalURL, err := url.Parse(urlConcat)
9696
if err != nil {
97+
logger.Errorf("===COUCHDB=== URL parse error: %s", err.Error())
9798
return nil, err
9899
}
99100

@@ -102,11 +103,11 @@ func CreateConnectionDefinition(host string, port int, databaseName, username, p
102103
logger.Debugf("===COUCHDB=== Exiting CreateConnectionDefinition()")
103104

104105
//return an object containing the connection information
105-
return &DBConnectionDef{finalURL.String(), username, password, databaseName}, nil
106+
return &CouchDBConnectionDef{finalURL.String(), username, password, databaseName}, nil
106107
}
107108

108109
//CreateDatabaseIfNotExist method provides function to create database
109-
func (dbclient *DBConnectionDef) CreateDatabaseIfNotExist() (*DBOperationResponse, error) {
110+
func (dbclient *CouchDBConnectionDef) CreateDatabaseIfNotExist() (*DBOperationResponse, error) {
110111

111112
logger.Debugf("===COUCHDB=== Entering CreateDatabaseIfNotExist()")
112113

@@ -154,7 +155,7 @@ func (dbclient *DBConnectionDef) CreateDatabaseIfNotExist() (*DBOperationRespons
154155
}
155156

156157
//GetDatabaseInfo method provides function to retrieve database information
157-
func (dbclient *DBConnectionDef) GetDatabaseInfo() (*DBInfo, *CouchDBReturn, error) {
158+
func (dbclient *CouchDBConnectionDef) GetDatabaseInfo() (*DBInfo, *CouchDBReturn, error) {
158159

159160
url := fmt.Sprintf("%s/%s", dbclient.URL, dbclient.Database)
160161

@@ -167,12 +168,20 @@ func (dbclient *DBConnectionDef) GetDatabaseInfo() (*DBInfo, *CouchDBReturn, err
167168
dbResponse := &DBInfo{}
168169
json.NewDecoder(resp.Body).Decode(&dbResponse)
169170

171+
// trace the database info response
172+
if logger.IsEnabledFor(logging.DEBUG) {
173+
dbResponseJSON, err := json.Marshal(dbResponse)
174+
if err == nil {
175+
logger.Debugf("===COUCHDB=== GetDatabaseInfo() dbResponseJSON: %s", dbResponseJSON)
176+
}
177+
}
178+
170179
return dbResponse, couchDBReturn, nil
171180

172181
}
173182

174183
//DropDatabase provides method to drop an existing database
175-
func (dbclient *DBConnectionDef) DropDatabase() (*DBOperationResponse, error) {
184+
func (dbclient *CouchDBConnectionDef) DropDatabase() (*DBOperationResponse, error) {
176185

177186
logger.Debugf("===COUCHDB=== Entering DropDatabase()")
178187

@@ -206,7 +215,7 @@ func (dbclient *DBConnectionDef) DropDatabase() (*DBOperationResponse, error) {
206215
}
207216

208217
//SaveDoc method provides a function to save a document, id and byte array
209-
func (dbclient *DBConnectionDef) SaveDoc(id string, bytesDoc []byte) (string, error) {
218+
func (dbclient *CouchDBConnectionDef) SaveDoc(id string, bytesDoc []byte) (string, error) {
210219

211220
logger.Debugf("===COUCHDB=== Entering SaveDoc()")
212221

@@ -246,7 +255,7 @@ func getRevisionHeader(resp *http.Response) (string, error) {
246255
}
247256

248257
//ReadDoc method provides function to retrieve a document from the database by id
249-
func (dbclient *DBConnectionDef) ReadDoc(id string) ([]byte, string, error) {
258+
func (dbclient *CouchDBConnectionDef) ReadDoc(id string) ([]byte, string, error) {
250259

251260
logger.Debugf("===COUCHDB=== Entering ReadDoc() id=%s", id)
252261

@@ -277,7 +286,7 @@ func (dbclient *DBConnectionDef) ReadDoc(id string) ([]byte, string, error) {
277286
}
278287

279288
//handleRequest method is a generic http request handler
280-
func (dbclient *DBConnectionDef) handleRequest(method, url string, data io.Reader) (*http.Response, *CouchDBReturn, error) {
289+
func (dbclient *CouchDBConnectionDef) handleRequest(method, url string, data io.Reader) (*http.Response, *CouchDBReturn, error) {
281290

282291
logger.Debugf("===COUCHDB=== Entering handleRequest() method=%s url=%s", method, url)
283292

core/ledger/kvledger/txmgmt/couchdbtxmgmt/couchdb_tx_simulator.go

+38-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"reflect"
2222

2323
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt"
24+
logging "github.com/op/go-logging"
2425
)
2526

2627
type kvReadCache struct {
@@ -62,19 +63,41 @@ func (s *CouchDBTxSimulator) GetState(ns string, key string) ([]byte, error) {
6263
// check if it was written
6364
kvWrite, ok := nsRWs.writeMap[key]
6465
if ok {
65-
logger.Debugf("Returing value for key=[%s:%s] from write set", ns, key, kvWrite.Value)
66+
// trace the first 500 bytes of value only, in case it is huge
67+
if logger.IsEnabledFor(logging.DEBUG) {
68+
if len(kvWrite.Value) < 500 {
69+
logger.Debugf("Returing value for key=[%s:%s] from write set", ns, key, kvWrite.Value)
70+
} else {
71+
logger.Debugf("Returing value for key=[%s:%s] from write set", ns, key, kvWrite.Value[0:500])
72+
}
73+
}
6674
return kvWrite.Value, nil
6775
}
6876
// check if it was read
6977
readCache, ok := nsRWs.readMap[key]
7078
if ok {
71-
logger.Debugf("Returing value for key=[%s:%s] from read set", ns, key, readCache.cachedValue)
79+
// trace the first 500 bytes of value only, in case it is huge
80+
if logger.IsEnabledFor(logging.DEBUG) {
81+
if len(readCache.cachedValue) < 500 {
82+
logger.Debugf("Returing value for key=[%s:%s] from read set", ns, key, readCache.cachedValue)
83+
} else {
84+
logger.Debugf("Returing value for key=[%s:%s] from read set", ns, key, readCache.cachedValue[0:500])
85+
}
86+
}
7287
return readCache.cachedValue, nil
7388
}
7489

7590
// read from storage
7691
value, version, err := s.txmgr.getCommittedValueAndVersion(ns, key)
77-
logger.Debugf("Read state from storage key=[%s:%s], value=[%#v], version=[%d]", ns, key, value, version)
92+
93+
// trace the first 500 bytes of value only, in case it is huge
94+
if logger.IsEnabledFor(logging.DEBUG) {
95+
if len(value) < 500 {
96+
logger.Debugf("Read state from storage key=[%s:%s], value=[%#v], version=[%d]", ns, key, value, version)
97+
} else {
98+
logger.Debugf("Read state from storage key=[%s:%s], value=[%#v...], version=[%d]", ns, key, value[0:500], version)
99+
}
100+
}
78101
if err != nil {
79102
return nil, err
80103
}
@@ -139,7 +162,17 @@ func (s *CouchDBTxSimulator) getTxReadWriteSet() *txmgmt.TxReadWriteSet {
139162
nsRWs := &txmgmt.NsReadWriteSet{NameSpace: ns, Reads: reads, Writes: writes}
140163
txRWSet.NsRWs = append(txRWSet.NsRWs, nsRWs)
141164
}
142-
logger.Debugf("txRWSet = [%s]", txRWSet)
165+
166+
// trace the first 2000 characters of RWSet only, in case it is huge
167+
if logger.IsEnabledFor(logging.DEBUG) {
168+
txRWSetString := txRWSet.String()
169+
if len(txRWSetString) < 2000 {
170+
logger.Debugf("txRWSet = [%s]", txRWSetString)
171+
} else {
172+
logger.Debugf("txRWSet = [%s...]", txRWSetString[0:2000])
173+
}
174+
}
175+
143176
return txRWSet
144177
}
145178

@@ -155,6 +188,7 @@ func getSortedKeys(m interface{}) []string {
155188

156189
// GetTxSimulationResults implements method in interface `ledger.TxSimulator`
157190
func (s *CouchDBTxSimulator) GetTxSimulationResults() ([]byte, error) {
191+
logger.Debugf("Simulation completed, getting simulation results")
158192
return s.getTxReadWriteSet().Marshal()
159193
}
160194

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package couchdbtxmgmt
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"testing"
23+
24+
"github.com/hyperledger/fabric/core/ledger/kvledger/kvledgerconfig"
25+
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/couchdbtxmgmt/couchdb"
26+
"github.com/hyperledger/fabric/core/ledger/testutil"
27+
)
28+
29+
type testEnv struct {
30+
conf *Conf
31+
couchHost string
32+
couchPort int
33+
couchDatabaseName string
34+
couchUsername string
35+
couchPassword string
36+
}
37+
38+
func newTestEnv(t testing.TB) *testEnv {
39+
conf := &Conf{"/tmp/tests/ledger/kvledger/txmgmt/couchdbtxmgmt"}
40+
os.RemoveAll(conf.DBPath)
41+
return &testEnv{
42+
conf: conf,
43+
couchHost: "127.0.0.1",
44+
couchPort: 5984,
45+
couchDatabaseName: "system_test",
46+
couchUsername: "",
47+
couchPassword: "",
48+
}
49+
}
50+
51+
func (env *testEnv) Cleanup() {
52+
os.RemoveAll(env.conf.DBPath)
53+
54+
//create a new connection
55+
couchDB, _ := couchdb.CreateConnectionDefinition(env.couchHost, env.couchPort, env.couchDatabaseName, env.couchUsername, env.couchPassword)
56+
57+
//drop the test database if it already existed
58+
couchDB.DropDatabase()
59+
}
60+
61+
// couchdb_test.go tests couchdb functions already. This test just tests that a CouchDB state database is auto-created
62+
// upon creating a new ledger transaction manager
63+
func TestDatabaseAutoCreate(t *testing.T) {
64+
65+
//Only run the tests if CouchDB is explitily enabled in the code,
66+
//otherwise CouchDB may not be installed and all the tests would fail
67+
//TODO replace this with external config property rather than config within the code
68+
if kvledgerconfig.IsCouchDBEnabled() == true {
69+
70+
env := newTestEnv(t)
71+
env.Cleanup() //cleanup at the beginning to ensure the database doesn't exist already
72+
defer env.Cleanup() //and cleanup at the end
73+
74+
txMgr := NewCouchDBTxMgr(env.conf,
75+
env.couchHost, //couchDB host
76+
env.couchPort, //couchDB port
77+
env.couchDatabaseName, //couchDB db name
78+
env.couchUsername, //enter couchDB id
79+
env.couchPassword) //enter couchDB pw
80+
81+
//NewCouchDBTxMgr should have automatically created the database, let's make sure it has been created
82+
//Retrieve the info for the new database and make sure the name matches
83+
dbResp, _, errdb := txMgr.couchDB.GetDatabaseInfo()
84+
testutil.AssertNoError(t, errdb, fmt.Sprintf("Error when trying to retrieve database information"))
85+
testutil.AssertEquals(t, dbResp.DbName, env.couchDatabaseName)
86+
87+
txMgr.Shutdown()
88+
89+
//Call NewCouchDBTxMgr again, this time the database will already exist from last time
90+
txMgr2 := NewCouchDBTxMgr(env.conf,
91+
env.couchHost, //couchDB host
92+
env.couchPort, //couchDB port
93+
env.couchDatabaseName, //couchDB db name
94+
env.couchUsername, //enter couchDB id
95+
env.couchPassword) //enter couchDB pw
96+
97+
//Retrieve the info for the database again, and make sure the name still matches
98+
dbResp2, _, errdb2 := txMgr.couchDB.GetDatabaseInfo()
99+
testutil.AssertNoError(t, errdb2, fmt.Sprintf("Error when trying to retrieve database information"))
100+
testutil.AssertEquals(t, dbResp2.DbName, env.couchDatabaseName)
101+
102+
txMgr2.Shutdown()
103+
104+
}
105+
106+
}

0 commit comments

Comments
 (0)