github.com/darrenli6/fabric-sdk-example@v0.0.0-20220109053535-94b13b56df8c/core/ledger/util/couchdb/couchdbutil.go (about) 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 couchdb 18 19 import ( 20 "fmt" 21 "net/http" 22 "regexp" 23 "strconv" 24 "strings" 25 "time" 26 ) 27 28 var expectedChannelNamePattern = `[a-z][a-z0-9.-]*` 29 var maxLength = 249 30 31 //CreateCouchInstance creates a CouchDB instance 32 func CreateCouchInstance(couchDBConnectURL, id, pw string, maxRetries, 33 maxRetriesOnStartup int, connectionTimeout time.Duration) (*CouchInstance, error) { 34 35 couchConf, err := CreateConnectionDefinition(couchDBConnectURL, 36 id, pw, maxRetries, maxRetriesOnStartup, connectionTimeout) 37 if err != nil { 38 logger.Errorf("Error during CouchDB CreateConnectionDefinition(): %s\n", err.Error()) 39 return nil, err 40 } 41 42 // Create the http client once 43 // Clients and Transports are safe for concurrent use by multiple goroutines 44 // and for efficiency should only be created once and re-used. 45 client := &http.Client{Timeout: couchConf.RequestTimeout} 46 47 transport := &http.Transport{Proxy: http.ProxyFromEnvironment} 48 transport.DisableCompression = false 49 client.Transport = transport 50 51 //Create the CouchDB instance 52 couchInstance := &CouchInstance{conf: *couchConf, client: client} 53 54 connectInfo, retVal, verifyErr := couchInstance.VerifyCouchConfig() 55 if verifyErr != nil { 56 return nil, verifyErr 57 } 58 59 //return an error if the http return value is not 200 60 if retVal.StatusCode != 200 { 61 return nil, fmt.Errorf("CouchDB connection error, expecting return code of 200, received %v", retVal.StatusCode) 62 } 63 64 //check the CouchDB version number, return an error if the version is not at least 2.0.0 65 errVersion := checkCouchDBVersion(connectInfo.Version) 66 if errVersion != nil { 67 return nil, errVersion 68 } 69 70 return couchInstance, nil 71 } 72 73 //checkCouchDBVersion verifies CouchDB is at least 2.0.0 74 func checkCouchDBVersion(version string) error { 75 76 //split the version into parts 77 majorVersion := strings.Split(version, ".") 78 79 //check to see that the major version number is at least 2 80 majorVersionInt, _ := strconv.Atoi(majorVersion[0]) 81 if majorVersionInt < 2 { 82 return fmt.Errorf("CouchDB must be at least version 2.0.0. Detected version %s", version) 83 } 84 85 return nil 86 } 87 88 //CreateCouchDatabase creates a CouchDB database object, as well as the underlying database if it does not exist 89 func CreateCouchDatabase(couchInstance CouchInstance, dbName string) (*CouchDatabase, error) { 90 91 databaseName, err := mapAndValidateDatabaseName(dbName) 92 if err != nil { 93 logger.Errorf("Error during CouchDB CreateDatabaseIfNotExist() for dbName: %s error: %s\n", dbName, err.Error()) 94 return nil, err 95 } 96 97 couchDBDatabase := CouchDatabase{CouchInstance: couchInstance, DBName: databaseName} 98 99 // Create CouchDB database upon ledger startup, if it doesn't already exist 100 _, err = couchDBDatabase.CreateDatabaseIfNotExist() 101 if err != nil { 102 logger.Errorf("Error during CouchDB CreateDatabaseIfNotExist() for dbName: %s error: %s\n", dbName, err.Error()) 103 return nil, err 104 } 105 106 return &couchDBDatabase, nil 107 } 108 109 //CreateSystemDatabasesIfNotExist - creates the system databases if they do not exist 110 func CreateSystemDatabasesIfNotExist(couchInstance CouchInstance) error { 111 112 dbName := "_users" 113 systemCouchDBDatabase := CouchDatabase{CouchInstance: couchInstance, DBName: dbName} 114 _, err := systemCouchDBDatabase.CreateDatabaseIfNotExist() 115 if err != nil { 116 logger.Errorf("Error during CouchDB CreateDatabaseIfNotExist() for system dbName: %s error: %s\n", dbName, err.Error()) 117 return err 118 } 119 120 dbName = "_replicator" 121 systemCouchDBDatabase = CouchDatabase{CouchInstance: couchInstance, DBName: dbName} 122 _, err = systemCouchDBDatabase.CreateDatabaseIfNotExist() 123 if err != nil { 124 logger.Errorf("Error during CouchDB CreateDatabaseIfNotExist() for system dbName: %s error: %s\n", dbName, err.Error()) 125 return err 126 } 127 128 dbName = "_global_changes" 129 systemCouchDBDatabase = CouchDatabase{CouchInstance: couchInstance, DBName: dbName} 130 _, err = systemCouchDBDatabase.CreateDatabaseIfNotExist() 131 if err != nil { 132 logger.Errorf("Error during CouchDB CreateDatabaseIfNotExist() for system dbName: %s error: %s\n", dbName, err.Error()) 133 return err 134 } 135 136 return nil 137 138 } 139 140 //mapAndValidateDatabaseName checks to see if the database name contains illegal characters 141 //CouchDB Rules: Only lowercase characters (a-z), digits (0-9), and any of the characters 142 //_, $, (, ), +, -, and / are allowed. Must begin with a letter. 143 // 144 //Restictions have already been applied to the database name from Orderer based on 145 //restrictions required by Kafka and couchDB (except a '.' char). The databaseName 146 // passed in here is expected to follow `[a-z][a-z0-9.-]*` pattern. 147 // 148 //This validation will simply check whether the database name matches the above pattern and will replace 149 // all occurence of '.' by '-'. This will not cause collisions in the trnasformed named 150 func mapAndValidateDatabaseName(databaseName string) (string, error) { 151 // test Length 152 if len(databaseName) <= 0 { 153 return "", fmt.Errorf("Database name is illegal, cannot be empty") 154 } 155 if len(databaseName) > maxLength { 156 return "", fmt.Errorf("Database name is illegal, cannot be longer than %d", maxLength) 157 } 158 re, err := regexp.Compile(expectedChannelNamePattern) 159 if err != nil { 160 return "", err 161 } 162 matched := re.FindString(databaseName) 163 if len(matched) != len(databaseName) { 164 return "", fmt.Errorf("databaseName '%s' does not matches pattern '%s'", databaseName, expectedChannelNamePattern) 165 } 166 // replace all '.' to '_'. The databaseName passed in will never contain an '_'. So, this translation will not cause collisions 167 databaseName = strings.Replace(databaseName, ".", "_", -1) 168 return databaseName, nil 169 }