This repository has been archived by the owner on Apr 5, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[#12] Add CouchDB implementation for id store
Signed-off-by: Firas Qutishat <[email protected]>
- Loading branch information
Showing
8 changed files
with
691 additions
and
132 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,11 @@ | ||
package testutil | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"time" | ||
|
||
"github.com/hyperledger/fabric/common/metrics/disabled" | ||
"github.com/hyperledger/fabric/core/ledger/util/couchdb" | ||
|
||
"github.com/hyperledger/fabric/common/flogging" | ||
"github.com/hyperledger/fabric/integration/runner" | ||
"github.com/spf13/viper" | ||
"github.com/trustbloc/fabric-peer-ext/pkg/testutil" | ||
) | ||
|
||
var logger = flogging.MustGetLogger("testutil") | ||
|
||
//SetupExtTestEnv creates new couchdb instance for test | ||
//returns couchdbd address, cleanup and stop function handle. | ||
func SetupExtTestEnv() (addr string, cleanup func(string), stop func()) { | ||
externalCouch, set := os.LookupEnv("COUCHDB_ADDR") | ||
if set { | ||
return externalCouch, func(string) {}, func() {} | ||
} | ||
|
||
couchDB := &runner.CouchDB{} | ||
if err := couchDB.Start(); err != nil { | ||
panic(fmt.Errorf("failed to start couchDB: %s", err)) | ||
} | ||
|
||
oldAddr := viper.GetString("ledger.state.couchDBConfig.couchDBAddress") | ||
|
||
//update config | ||
updateConfig(couchDB.Address()) | ||
|
||
return couchDB.Address(), | ||
func(name string) { | ||
cleanupCouchDB(name, couchDB) | ||
}, func() { | ||
//reset viper cdb config | ||
updateConfig(oldAddr) | ||
if err := couchDB.Stop(); err != nil { | ||
panic(err.Error()) | ||
} | ||
} | ||
} | ||
|
||
func cleanupCouchDB(name string, couchDB *runner.CouchDB) { | ||
couchDBDef := couchdb.GetCouchDBDefinition() | ||
couchInstance, _ := couchdb.CreateCouchInstance(couchDB.Address(), couchDBDef.Username, couchDBDef.Password, | ||
couchDBDef.MaxRetries, couchDBDef.MaxRetriesOnStartup, couchDBDef.RequestTimeout, couchDBDef.CreateGlobalChangesDB, &disabled.Provider{}) | ||
|
||
blkdb := couchdb.CouchDatabase{CouchInstance: couchInstance, DBName: fmt.Sprintf("%s$$blocks_", name)} | ||
pvtdb := couchdb.CouchDatabase{CouchInstance: couchInstance, DBName: fmt.Sprintf("%s$$pvtdata_", name)} | ||
txndb := couchdb.CouchDatabase{CouchInstance: couchInstance, DBName: fmt.Sprintf("%s$$transactions_", name)} | ||
|
||
//drop the test databases | ||
_, err := blkdb.DropDatabase() | ||
if err != nil { | ||
logger.Warnf("Failed to drop db: %s, cause:%s", blkdb.DBName, err) | ||
} | ||
_, err = pvtdb.DropDatabase() | ||
if err != nil { | ||
logger.Warnf("Failed to drop db: %s, cause:%s", pvtdb.DBName, err) | ||
} | ||
_, err = txndb.DropDatabase() | ||
if err != nil { | ||
logger.Warnf("Failed to drop db: %s, cause:%s", txndb.DBName, err) | ||
} | ||
} | ||
|
||
//updateConfig updates 'couchAddress' in config | ||
func updateConfig(couchAddress string) { | ||
|
||
viper.Set("ledger.state.couchDBConfig.couchDBAddress", couchAddress) | ||
// Replace with correct username/password such as | ||
// admin/admin if user security is enabled on couchdb. | ||
viper.Set("ledger.state.couchDBConfig.username", "") | ||
viper.Set("ledger.state.couchDBConfig.password", "") | ||
viper.Set("ledger.state.couchDBConfig.maxRetries", 1) | ||
viper.Set("ledger.state.couchDBConfig.maxRetriesOnStartup", 1) | ||
viper.Set("ledger.state.couchDBConfig.requestTimeout", time.Second*35) | ||
viper.Set("ledger.state.couchDBConfig.createGlobalChangesDB", false) | ||
|
||
return testutil.SetupExtTestEnv() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* | ||
Copyright SecureKey Technologies Inc. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package idstore | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/golang/protobuf/proto" | ||
"github.com/hyperledger/fabric/core/ledger/util/couchdb" | ||
"github.com/hyperledger/fabric/protos/common" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
const ( | ||
idField = "_id" | ||
underConstructionLedgerKey = "under_construction" | ||
ledgerKeyPrefix = "ledger_" | ||
metadataKey = "metadata" | ||
blockAttachmentName = "genesis_block" | ||
inventoryTypeField = "type" | ||
inventoryTypeIndexName = "by_type" | ||
inventoryTypeIndexDoc = "indexMetadataInventory" | ||
inventoryNameLedgerIDField = "ledger_id" | ||
typeLedgerName = "ledger" | ||
) | ||
|
||
const inventoryTypeIndexDef = ` | ||
{ | ||
"index": { | ||
"fields": ["` + inventoryTypeField + `"] | ||
}, | ||
"name": "` + inventoryTypeIndexName + `", | ||
"ddoc": "` + inventoryTypeIndexDoc + `", | ||
"type": "json" | ||
}` | ||
|
||
type jsonValue map[string]interface{} | ||
|
||
func (v jsonValue) toBytes() ([]byte, error) { | ||
return json.Marshal(v) | ||
} | ||
|
||
func ledgerToCouchDoc(ledgerID string, gb *common.Block) (*couchdb.CouchDoc, error) { | ||
jsonMap := make(jsonValue) | ||
|
||
jsonMap[idField] = ledgerIDToKey(ledgerID) | ||
jsonMap[inventoryTypeField] = typeLedgerName | ||
jsonMap[inventoryNameLedgerIDField] = ledgerID | ||
|
||
jsonBytes, err := jsonMap.toBytes() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
couchDoc := couchdb.CouchDoc{JSONValue: jsonBytes} | ||
|
||
attachment, err := blockToAttachment(gb) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
attachments := append([]*couchdb.AttachmentInfo{}, attachment) | ||
couchDoc.Attachments = attachments | ||
|
||
return &couchDoc, nil | ||
} | ||
|
||
func createMetadataDoc(constructionLedger string) (*couchdb.CouchDoc, error) { | ||
jsonMap := make(jsonValue) | ||
|
||
jsonMap[idField] = metadataKey | ||
jsonMap[underConstructionLedgerKey] = constructionLedger | ||
|
||
jsonBytes, err := jsonMap.toBytes() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
couchDoc := couchdb.CouchDoc{JSONValue: jsonBytes} | ||
|
||
return &couchDoc, nil | ||
} | ||
|
||
func ledgerIDToKey(ledgerID string) string { | ||
return fmt.Sprintf(ledgerKeyPrefix+"%s", ledgerID) | ||
} | ||
|
||
func blockToAttachment(block *common.Block) (*couchdb.AttachmentInfo, error) { | ||
blockBytes, err := proto.Marshal(block) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "marshaling block failed") | ||
} | ||
|
||
attachment := &couchdb.AttachmentInfo{} | ||
attachment.AttachmentBytes = blockBytes | ||
attachment.ContentType = "application/octet-stream" | ||
attachment.Name = blockAttachmentName | ||
|
||
return attachment, nil | ||
} | ||
|
||
func couchDocToJSON(doc *couchdb.CouchDoc) (jsonValue, error) { | ||
return couchValueToJSON(doc.JSONValue) | ||
} | ||
|
||
func couchValueToJSON(value []byte) (jsonValue, error) { | ||
// create a generic map unmarshal the json | ||
jsonResult := make(map[string]interface{}) | ||
decoder := json.NewDecoder(bytes.NewBuffer(value)) | ||
decoder.UseNumber() | ||
|
||
err := decoder.Decode(&jsonResult) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "result from DB is not JSON encoded") | ||
} | ||
|
||
return jsonResult, nil | ||
} | ||
|
||
func queryInventory(db *couchdb.CouchDatabase, inventoryType string) ([]*couchdb.QueryResult, error) { | ||
const queryFmt = ` | ||
{ | ||
"selector": { | ||
"` + inventoryTypeField + `": { | ||
"$eq": "%s" | ||
} | ||
}, | ||
"use_index": ["_design/` + inventoryTypeIndexDoc + `", "` + inventoryTypeIndexName + `"] | ||
}` | ||
|
||
results, _, err := db.QueryDocuments(fmt.Sprintf(queryFmt, inventoryType)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return results, nil | ||
} |
Oops, something went wrong.