github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/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 validNamePattern = `^[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 146 // 147 //The validation will validate upper case, the string will be lower cased 148 //Replace any characters not allowed in CouchDB with an "_" 149 //Check for a leading letter, if not present, the prepend "db_" 150 func mapAndValidateDatabaseName(databaseName string) (string, error) { 151 152 // test Length 153 if len(databaseName) <= 0 { 154 return "", fmt.Errorf("Database name is illegal, cannot be empty") 155 } 156 if len(databaseName) > maxLength { 157 return "", fmt.Errorf("Database name is illegal, cannot be longer than %d", maxLength) 158 } 159 160 //force the name to all lowercase 161 databaseName = strings.ToLower(databaseName) 162 163 //Replace any characters not allowed in CouchDB with an "_" 164 replaceString := regexp.MustCompile(`[^a-z0-9_$(),+/-]`) 165 166 //Set up the replace pattern for special characters 167 validatedDatabaseName := replaceString.ReplaceAllString(databaseName, "_") 168 169 //if the first character is not a letter, then prepend "db_" 170 testLeadingLetter := regexp.MustCompile("^[a-z]") 171 isLeadingLetter := testLeadingLetter.MatchString(validatedDatabaseName) 172 if !isLeadingLetter { 173 validatedDatabaseName = "db_" + validatedDatabaseName 174 } 175 176 //create the expression for valid characters 177 validString := regexp.MustCompile(validNamePattern) 178 179 // Illegal characters 180 matched := validString.MatchString(validatedDatabaseName) 181 if !matched { 182 return "", fmt.Errorf("Database name '%s' contains illegal characters", validatedDatabaseName) 183 } 184 return validatedDatabaseName, nil 185 }