Skip to content
This repository has been archived by the owner on Apr 5, 2023. It is now read-only.

Commit

Permalink
[#12] Add CouchDB implementation for id store
Browse files Browse the repository at this point in the history
Signed-off-by: Firas Qutishat <[email protected]>
  • Loading branch information
fqutishat authored May 10, 2019
1 parent d3d493a commit 2f9256a
Show file tree
Hide file tree
Showing 8 changed files with 691 additions and 132 deletions.
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# unit-test: runs unit tests
# lint: runs linters
# checks: runs code checks
# docker-thirdparty pulls thirdparty images (couchdb)
#

CONTAINER_IDS = $(shell docker ps -a -q)
Expand All @@ -25,16 +26,19 @@ checks: version depend lint
lint:
@scripts/check_lint.sh

all: checks fabric-unit-test
all: checks unit-test

unit-test: export FABRIC_COMMAND=unit-test
unit-test: checks
unit-test: checks docker-thirdparty
@scripts/unit.sh
@scripts/build_fabric.sh

version:
@scripts/check_version.sh

docker-thirdparty:
docker pull couchdb:2.2.0

clean-images:
@echo "Stopping all containers, pruning containers and images, deleting dev images"
ifneq ($(strip $(CONTAINER_IDS)),)
Expand All @@ -46,4 +50,4 @@ ifneq ($(strip $(DEV_IMAGES)),)
endif
@docker rmi $(docker images securekey/* -aq)

.PHONY: all version clean-images fabric-unit-test
.PHONY: all version clean-images unit-test docker-thirdparty
56 changes: 3 additions & 53 deletions go.sum

Large diffs are not rendered by default.

78 changes: 2 additions & 76 deletions mod/peer/testutil/ext_test_env.go
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()
}
142 changes: 142 additions & 0 deletions pkg/idstore/couchdoc_conv.go
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
}
Loading

0 comments on commit 2f9256a

Please sign in to comment.