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  }