gitee.com/zhaochuninhefei/fabric-ca-gm@v0.0.2/lib/server/db/mysql/mysql.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package mysql
     8  
     9  import (
    10  	"context"
    11  	"database/sql"
    12  	"fmt"
    13  	"regexp"
    14  	"strings"
    15  
    16  	"gitee.com/zhaochuninhefei/fabric-ca-gm/go-sql-driver/mysql"
    17  	"gitee.com/zhaochuninhefei/fabric-ca-gm/lib/server/db"
    18  	"gitee.com/zhaochuninhefei/fabric-ca-gm/lib/server/db/util"
    19  	"gitee.com/zhaochuninhefei/fabric-ca-gm/lib/tls"
    20  	"gitee.com/zhaochuninhefei/fabric-gm/bccsp"
    21  	log "gitee.com/zhaochuninhefei/zcgolog/zclog"
    22  	"github.com/jmoiron/sqlx"
    23  	"github.com/pkg/errors"
    24  )
    25  
    26  var (
    27  	re = regexp.MustCompile(`\/([0-9,a-z,A-Z$_]+)`)
    28  )
    29  
    30  // Mysql defines MySQL database
    31  type Mysql struct {
    32  	SqlxDB  db.FabricCADB
    33  	TLS     *tls.ClientTLSConfig
    34  	CSP     bccsp.BCCSP
    35  	CAName  string
    36  	Metrics *db.Metrics
    37  
    38  	datasource string
    39  	dbName     string
    40  }
    41  
    42  // NewDB create a MySQL database
    43  func NewDB(
    44  	datasource,
    45  	caName string,
    46  	clientTLSConfig *tls.ClientTLSConfig,
    47  	csp bccsp.BCCSP,
    48  	metrics *db.Metrics,
    49  ) *Mysql {
    50  	log.Debugf("Using MySQL database, connecting to database...")
    51  	return &Mysql{
    52  		TLS:        clientTLSConfig,
    53  		CSP:        csp,
    54  		datasource: datasource,
    55  		CAName:     caName,
    56  		Metrics:    metrics,
    57  	}
    58  }
    59  
    60  // Connect connects to a MySQL server
    61  func (m *Mysql) Connect() error {
    62  	datasource := m.datasource
    63  	clientTLSConfig := m.TLS
    64  
    65  	m.dbName = util.GetDBName(datasource)
    66  	log.Debugf("Database Name: %s", m.dbName)
    67  
    68  	connStr := re.ReplaceAllString(datasource, "/")
    69  
    70  	if clientTLSConfig.Enabled {
    71  		tlsConfig, err := tls.GetClientTLSConfig(clientTLSConfig, m.CSP)
    72  		if err != nil {
    73  			return errors.WithMessage(err, "Failed to get client TLS for MySQL")
    74  		}
    75  
    76  		mysql.RegisterTLSConfig("custom", tlsConfig)
    77  	}
    78  
    79  	log.Debugf("Connecting to MySQL server, using connection string: %s", util.MaskDBCred(connStr))
    80  	sqlxdb, err := sqlx.Connect("mysql", connStr)
    81  	if err != nil {
    82  		return errors.Wrap(err, "Failed to connect to MySQL database")
    83  	}
    84  
    85  	m.SqlxDB = db.New(sqlxdb, m.CAName, m.Metrics)
    86  	return nil
    87  }
    88  
    89  // PingContext pings the database
    90  func (m *Mysql) PingContext(ctx context.Context) error {
    91  	err := m.SqlxDB.PingContext(ctx)
    92  	if err != nil {
    93  		return errors.Wrap(err, "Failed to ping to MySQL database")
    94  	}
    95  	return nil
    96  }
    97  
    98  // exists determines if the database has already been created
    99  func exists(sqlxDB db.FabricCADB, dbName string) (bool, error) {
   100  	log.Debugf("Checking if MySQL CA database '%s' exists", dbName)
   101  	var exists bool
   102  	query := fmt.Sprintf("SELECT true as 'exists' FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '%s'", dbName)
   103  	err := sqlxDB.Get("CheckIfDatabaseExists", &exists, query)
   104  	if err != nil && err != sql.ErrNoRows {
   105  		return false, errors.Wrapf(err, "Failed to check if MySQL CA database '%s' exists", dbName)
   106  	}
   107  	return exists, nil
   108  }
   109  
   110  // Create creates database and tables
   111  func (m *Mysql) Create() (*db.DB, error) {
   112  	db, err := m.CreateDatabase()
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	err = m.CreateTables()
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	return db, nil
   121  }
   122  
   123  // CreateDatabase creates database
   124  func (m *Mysql) CreateDatabase() (*db.DB, error) {
   125  	err := m.createDatabase()
   126  	if err != nil {
   127  		return nil, errors.Wrap(err, "Failed to create MySQL database")
   128  	}
   129  
   130  	log.Debugf("Connecting to database '%s', using connection string: '%s'", m.dbName, util.MaskDBCred(m.datasource))
   131  	sqlxdb, err := sqlx.Open("mysql", m.datasource)
   132  	if err != nil {
   133  		return nil, errors.Wrapf(err, "Failed to open database '%s' in MySQL server", m.dbName)
   134  	}
   135  	m.SqlxDB = db.New(sqlxdb, m.CAName, m.Metrics)
   136  
   137  	return m.SqlxDB.(*db.DB), nil
   138  }
   139  
   140  // CreateTables creates table
   141  func (m *Mysql) CreateTables() error {
   142  	err := m.createTables()
   143  	if err != nil {
   144  		return errors.Wrap(err, "Failed to create MySQL tables")
   145  	}
   146  	return nil
   147  }
   148  
   149  func (m *Mysql) createDatabase() error {
   150  	exists, err := exists(m.SqlxDB, m.dbName)
   151  	if err != nil {
   152  		return err
   153  	}
   154  	if !exists {
   155  		log.Debugf("Creating MySQL Database '%s'", m.dbName)
   156  		_, err := m.SqlxDB.Exec("CreateDatabase", "CREATE DATABASE "+m.dbName)
   157  		if err != nil {
   158  			return errors.Wrap(err, "Failed to execute create database query")
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  func (m *Mysql) createTables() error {
   165  	db := m.SqlxDB
   166  	log.Debug("Creating users table if it doesn't exist")
   167  	if _, err := db.Exec("CreateUsersTable", "CREATE TABLE IF NOT EXISTS users (id VARCHAR(255) NOT NULL, token blob, type VARCHAR(256), affiliation VARCHAR(1024), attributes TEXT, state INTEGER, max_enrollments INTEGER, level INTEGER DEFAULT 0, incorrect_password_attempts INTEGER DEFAULT 0, PRIMARY KEY (id)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil {
   168  		return errors.Wrap(err, "Error creating users table")
   169  	}
   170  	log.Debug("Creating affiliations table if it doesn't exist")
   171  	if _, err := db.Exec("CreateAffiliationsTable", "CREATE TABLE IF NOT EXISTS affiliations (id INT NOT NULL AUTO_INCREMENT, name VARCHAR(1024) NOT NULL, prekey VARCHAR(1024), level INTEGER DEFAULT 0, PRIMARY KEY (id)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil {
   172  		return errors.Wrap(err, "Error creating affiliations table")
   173  	}
   174  	log.Debug("Creating index on 'name' in the affiliations table")
   175  	if _, err := db.Exec("CreateAffiliationsIndex", "CREATE INDEX name_index on affiliations (name)"); err != nil {
   176  		if !strings.Contains(err.Error(), "Error 1061") { // Error 1061: Duplicate key name, index already exists
   177  			return errors.Wrap(err, "Error creating index on affiliations table")
   178  		}
   179  	}
   180  	log.Debug("Creating certificates table if it doesn't exist")
   181  	if _, err := db.Exec("CreateCertificatesTable", "CREATE TABLE IF NOT EXISTS certificates (id VARCHAR(255), serial_number varbinary(128) NOT NULL, authority_key_identifier varbinary(128) NOT NULL, ca_label varbinary(128), status varbinary(128) NOT NULL, reason int, expiry timestamp DEFAULT 0, revoked_at timestamp DEFAULT 0, pem varbinary(8192) NOT NULL, level INTEGER DEFAULT 0, PRIMARY KEY(serial_number, authority_key_identifier)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil {
   182  		return errors.Wrap(err, "Error creating certificates table")
   183  	}
   184  	log.Debug("Creating credentials table if it doesn't exist")
   185  	if _, err := db.Exec("CreateCredentialsTable", "CREATE TABLE IF NOT EXISTS credentials (id VARCHAR(255), revocation_handle varbinary(128) NOT NULL, cred varbinary(4096) NOT NULL, ca_label varbinary(128), status varbinary(128) NOT NULL, reason int, expiry timestamp DEFAULT 0, revoked_at timestamp DEFAULT 0, level INTEGER DEFAULT 0, PRIMARY KEY(revocation_handle)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil {
   186  		return errors.Wrap(err, "Error creating credentials table")
   187  	}
   188  	log.Debug("Creating revocation_authority_info table if it does not exist")
   189  	if _, err := db.Exec("CreateRevocationAuthorityTable", "CREATE TABLE IF NOT EXISTS revocation_authority_info (epoch INTEGER, next_handle INTEGER, lasthandle_in_pool INTEGER, level INTEGER DEFAULT 0, PRIMARY KEY (epoch)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil {
   190  		return errors.Wrap(err, "Error creating revocation_authority_info table")
   191  	}
   192  	log.Debug("Creating nonces table if it does not exist")
   193  	if _, err := db.Exec("CreateNoncesTable", "CREATE TABLE IF NOT EXISTS nonces (val VARCHAR(255) NOT NULL, expiry timestamp, level INTEGER DEFAULT 0, PRIMARY KEY (val)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil {
   194  		return errors.Wrap(err, "Error creating nonces table")
   195  	}
   196  	log.Debug("Creating properties table if it does not exist")
   197  	if _, err := db.Exec("CreatePropertiesTable", "CREATE TABLE IF NOT EXISTS properties (property VARCHAR(255), value VARCHAR(256), PRIMARY KEY(property)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil {
   198  		return errors.Wrap(err, "Error creating properties table")
   199  	}
   200  	_, err := db.Exec("CreatePropertiesTable", db.Rebind("INSERT INTO properties (property, value) VALUES ('identity.level', '0'), ('affiliation.level', '0'), ('certificate.level', '0'), ('credential.level', '0'), ('rcinfo.level', '0'), ('nonce.level', '0')"))
   201  	if err != nil {
   202  		if !strings.Contains(err.Error(), "1062") { // MySQL error code for duplicate entry
   203  			return err
   204  		}
   205  	}
   206  	return nil
   207  }