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  }