Skip to content

Commit 148438e

Browse files
author
Chris Elder
committed
FAB-1925 Verify CouchDB connection upon peer startup
Motivation for this change: Upon peer startup, if CouchDB is configured for state database, need to verify connection to CouchDB. If can't connect to CouchDB provide a good error and halt peer startup. Currently, it fails upon the first chain creation step: Creating KVLedger ledgerID=myc1. Should check connection upon KVLedger provider creation. - Add connection verification to couchdb Change-Id: I3a3dc91463f230af1ee08b55ae595ece049eb4eb Signed-off-by: Chris Elder <[email protected]>
1 parent 22ede47 commit 148438e

File tree

3 files changed

+109
-29
lines changed

3 files changed

+109
-29
lines changed

core/ledger/util/couchdb/couchdb.go

+55-12
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ type DBInfo struct {
6969
InstanceStartTime string `json:"instance_start_time"`
7070
}
7171

72+
//ConnectionInfo is a structure for capturing the database info and version
73+
type ConnectionInfo struct {
74+
Couchdb string `json:"couchdb"`
75+
Version string `json:"version"`
76+
Vendor struct {
77+
Name string `json:"name"`
78+
} `json:"vendor"`
79+
}
80+
7281
//RangeQueryResponse is used for processing REST range query responses from CouchDB
7382
type RangeQueryResponse struct {
7483
TotalRows int `json:"total_rows"`
@@ -211,7 +220,7 @@ func (dbclient *CouchDatabase) CreateDatabaseIfNotExist() (*DBOperationResponse,
211220
connectURL.Path = dbclient.dbName
212221

213222
//process the URL with a PUT, creates the database
214-
resp, _, err := dbclient.handleRequest(http.MethodPut, connectURL.String(), nil, "", "")
223+
resp, _, err := dbclient.couchInstance.handleRequest(http.MethodPut, connectURL.String(), nil, "", "")
215224
if err != nil {
216225
return nil, err
217226
}
@@ -249,7 +258,7 @@ func (dbclient *CouchDatabase) GetDatabaseInfo() (*DBInfo, *DBReturn, error) {
249258
}
250259
connectURL.Path = dbclient.dbName
251260

252-
resp, couchDBReturn, err := dbclient.handleRequest(http.MethodGet, connectURL.String(), nil, "", "")
261+
resp, couchDBReturn, err := dbclient.couchInstance.handleRequest(http.MethodGet, connectURL.String(), nil, "", "")
253262
if err != nil {
254263
return nil, couchDBReturn, err
255264
}
@@ -270,6 +279,40 @@ func (dbclient *CouchDatabase) GetDatabaseInfo() (*DBInfo, *DBReturn, error) {
270279

271280
}
272281

282+
//VerifyConnection method provides function to verify the connection information
283+
func (couchInstance *CouchInstance) VerifyConnection() (*ConnectionInfo, *DBReturn, error) {
284+
285+
connectURL, err := url.Parse(couchInstance.conf.URL)
286+
if err != nil {
287+
logger.Errorf("URL parse error: %s", err.Error())
288+
return nil, nil, err
289+
}
290+
connectURL.Path = "/"
291+
292+
resp, couchDBReturn, err := couchInstance.handleRequest(http.MethodGet, connectURL.String(), nil, "", "")
293+
if err != nil {
294+
return nil, couchDBReturn, err
295+
}
296+
defer resp.Body.Close()
297+
298+
dbResponse := &ConnectionInfo{}
299+
errJSON := json.NewDecoder(resp.Body).Decode(&dbResponse)
300+
if errJSON != nil {
301+
return nil, nil, errJSON
302+
}
303+
304+
// trace the database info response
305+
if logger.IsEnabledFor(logging.DEBUG) {
306+
dbResponseJSON, err := json.Marshal(dbResponse)
307+
if err == nil {
308+
logger.Debugf("VerifyConnection() dbResponseJSON: %s", dbResponseJSON)
309+
}
310+
}
311+
312+
return dbResponse, couchDBReturn, nil
313+
314+
}
315+
273316
//DropDatabase provides method to drop an existing database
274317
func (dbclient *CouchDatabase) DropDatabase() (*DBOperationResponse, error) {
275318

@@ -282,7 +325,7 @@ func (dbclient *CouchDatabase) DropDatabase() (*DBOperationResponse, error) {
282325
}
283326
connectURL.Path = dbclient.dbName
284327

285-
resp, _, err := dbclient.handleRequest(http.MethodDelete, connectURL.String(), nil, "", "")
328+
resp, _, err := dbclient.couchInstance.handleRequest(http.MethodDelete, connectURL.String(), nil, "", "")
286329
if err != nil {
287330
return nil, err
288331
}
@@ -319,7 +362,7 @@ func (dbclient *CouchDatabase) EnsureFullCommit() (*DBOperationResponse, error)
319362
}
320363
connectURL.Path = dbclient.dbName + "/_ensure_full_commit"
321364

322-
resp, _, err := dbclient.handleRequest(http.MethodPost, connectURL.String(), nil, "", "")
365+
resp, _, err := dbclient.couchInstance.handleRequest(http.MethodPost, connectURL.String(), nil, "", "")
323366
if err != nil {
324367
logger.Errorf("Failed to invoke _ensure_full_commit Error: %s\n", err.Error())
325368
return nil, err
@@ -413,7 +456,7 @@ func (dbclient *CouchDatabase) SaveDoc(id string, rev string, couchDoc *CouchDoc
413456
}
414457

415458
//handle the request for saving the JSON or attachments
416-
resp, _, err := dbclient.handleRequest(http.MethodPut, saveURL.String(), data, rev, defaultBoundary)
459+
resp, _, err := dbclient.couchInstance.handleRequest(http.MethodPut, saveURL.String(), data, rev, defaultBoundary)
417460
if err != nil {
418461
return "", err
419462
}
@@ -536,7 +579,7 @@ func (dbclient *CouchDatabase) ReadDoc(id string) (*CouchDoc, string, error) {
536579

537580
readURL.RawQuery = query.Encode()
538581

539-
resp, couchDBReturn, err := dbclient.handleRequest(http.MethodGet, readURL.String(), nil, "", "")
582+
resp, couchDBReturn, err := dbclient.couchInstance.handleRequest(http.MethodGet, readURL.String(), nil, "", "")
540583
if err != nil {
541584
fmt.Printf("couchDBReturn=%v", couchDBReturn)
542585
if couchDBReturn != nil && couchDBReturn.StatusCode == 404 {
@@ -686,7 +729,7 @@ func (dbclient *CouchDatabase) ReadDocRange(startKey, endKey string, limit, skip
686729

687730
rangeURL.RawQuery = queryParms.Encode()
688731

689-
resp, _, err := dbclient.handleRequest(http.MethodGet, rangeURL.String(), nil, "", "")
732+
resp, _, err := dbclient.couchInstance.handleRequest(http.MethodGet, rangeURL.String(), nil, "", "")
690733
if err != nil {
691734
return nil, err
692735
}
@@ -781,7 +824,7 @@ func (dbclient *CouchDatabase) DeleteDoc(id, rev string) error {
781824

782825
logger.Debugf(" rev=%s", rev)
783826

784-
resp, couchDBReturn, err := dbclient.handleRequest(http.MethodDelete, deleteURL.String(), nil, rev, "")
827+
resp, couchDBReturn, err := dbclient.couchInstance.handleRequest(http.MethodDelete, deleteURL.String(), nil, rev, "")
785828
if err != nil {
786829
fmt.Printf("couchDBReturn=%v", couchDBReturn)
787830
if couchDBReturn != nil && couchDBReturn.StatusCode == 404 {
@@ -826,7 +869,7 @@ func (dbclient *CouchDatabase) QueryDocuments(query string, limit, skip int) (*[
826869

827870
data.ReadFrom(bytes.NewReader([]byte(query)))
828871

829-
resp, _, err := dbclient.handleRequest(http.MethodPost, queryURL.String(), data, "", "")
872+
resp, _, err := dbclient.couchInstance.handleRequest(http.MethodPost, queryURL.String(), data, "", "")
830873
if err != nil {
831874
return nil, err
832875
}
@@ -887,7 +930,7 @@ func (dbclient *CouchDatabase) QueryDocuments(query string, limit, skip int) (*[
887930
}
888931

889932
//handleRequest method is a generic http request handler
890-
func (dbclient *CouchDatabase) handleRequest(method, connectURL string, data io.Reader, rev string, multipartBoundary string) (*http.Response, *DBReturn, error) {
933+
func (couchInstance *CouchInstance) handleRequest(method, connectURL string, data io.Reader, rev string, multipartBoundary string) (*http.Response, *DBReturn, error) {
891934

892935
logger.Debugf("Entering handleRequest() method=%s url=%v", method, connectURL)
893936

@@ -925,8 +968,8 @@ func (dbclient *CouchDatabase) handleRequest(method, connectURL string, data io.
925968
}
926969

927970
//If username and password are set the use basic auth
928-
if dbclient.couchInstance.conf.Username != "" && dbclient.couchInstance.conf.Password != "" {
929-
req.SetBasicAuth(dbclient.couchInstance.conf.Username, dbclient.couchInstance.conf.Password)
971+
if couchInstance.conf.Username != "" && couchInstance.conf.Password != "" {
972+
req.SetBasicAuth(couchInstance.conf.Username, couchInstance.conf.Password)
930973
}
931974

932975
if logger.IsEnabledFor(logging.DEBUG) {

core/ledger/util/couchdb/couchdb_test.go

+18-16
Original file line numberDiff line numberDiff line change
@@ -153,22 +153,8 @@ func TestDBBadConnection(t *testing.T) {
153153
if ledgerconfig.IsCouchDBEnabled() == true {
154154

155155
//create a new instance and database object
156-
couchInstance, err := CreateCouchInstance(badConnectURL, username, password)
157-
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to create couch instance"))
158-
db := CouchDatabase{couchInstance: *couchInstance, dbName: database}
159-
160-
//create a new database
161-
_, errdb := db.CreateDatabaseIfNotExist()
162-
testutil.AssertError(t, errdb, fmt.Sprintf("Error should have been thrown while creating a database with an invalid connecion"))
163-
164-
//Save the test document
165-
_, saveerr := db.SaveDoc("3", "", &CouchDoc{JSONValue: assetJSON, Attachments: nil})
166-
testutil.AssertError(t, saveerr, fmt.Sprintf("Error should have been thrown while saving a document with an invalid connecion"))
167-
168-
//Retrieve the updated test document
169-
_, _, geterr := db.ReadDoc("3")
170-
testutil.AssertError(t, geterr, fmt.Sprintf("Error should have been thrown while retrieving a document with an invalid connecion"))
171-
156+
_, err := CreateCouchInstance(badConnectURL, username, password)
157+
testutil.AssertError(t, err, fmt.Sprintf("Error should have been thrown for a bad connection"))
172158
}
173159
}
174160

@@ -461,3 +447,19 @@ func TestDBDeleteNonExistingDocument(t *testing.T) {
461447
}
462448
}
463449
}
450+
451+
func TestCouchDBVersion(t *testing.T) {
452+
453+
err := checkCouchDBVersion("2.0.0")
454+
testutil.AssertNoError(t, err, fmt.Sprintf("Error should not have been thrown for valid version"))
455+
456+
err = checkCouchDBVersion("4.5.0")
457+
testutil.AssertNoError(t, err, fmt.Sprintf("Error should not have been thrown for valid version"))
458+
459+
err = checkCouchDBVersion("1.6.5.4")
460+
testutil.AssertError(t, err, fmt.Sprintf("Error should have been thrown for invalid version"))
461+
462+
err = checkCouchDBVersion("0.0.0.0")
463+
testutil.AssertError(t, err, fmt.Sprintf("Error should have been thrown for invalid version"))
464+
465+
}

core/ledger/util/couchdb/couchdbutil.go

+36-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package couchdb
1919
import (
2020
"fmt"
2121
"regexp"
22+
"strconv"
2223
"strings"
2324
)
2425

@@ -35,7 +36,41 @@ func CreateCouchInstance(couchDBConnectURL string, id string, pw string) (*Couch
3536
return nil, err
3637
}
3738

38-
return &CouchInstance{conf: *couchConf}, nil
39+
//Create the CouchDB instance
40+
couchInstance := &CouchInstance{conf: *couchConf}
41+
42+
connectInfo, retVal, verifyErr := couchInstance.VerifyConnection()
43+
if verifyErr != nil {
44+
return nil, fmt.Errorf("Unable to connect to CouchDB, check the hostname and port: %s", verifyErr.Error())
45+
}
46+
47+
//return an error if the http return value is not 200
48+
if retVal.StatusCode != 200 {
49+
return nil, fmt.Errorf("CouchDB connection error, expecting return code of 200, received %v", retVal.StatusCode)
50+
}
51+
52+
//check the CouchDB version number, return an error if the version is not at least 2.0.0
53+
errVersion := checkCouchDBVersion(connectInfo.Version)
54+
if errVersion != nil {
55+
return nil, errVersion
56+
}
57+
58+
return couchInstance, nil
59+
}
60+
61+
//checkCouchDBVersion verifies CouchDB is at least 2.0.0
62+
func checkCouchDBVersion(version string) error {
63+
64+
//split the version into parts
65+
majorVersion := strings.Split(version, ".")
66+
67+
//check to see that the major version number is at least 2
68+
majorVersionInt, _ := strconv.Atoi(majorVersion[0])
69+
if majorVersionInt < 2 {
70+
return fmt.Errorf("CouchDB must be at least version 2.0.0. Detected version %s", version)
71+
}
72+
73+
return nil
3974
}
4075

4176
//CreateCouchDatabase creates a CouchDB database object, as well as the underlying database if it does not exist

0 commit comments

Comments
 (0)