github.com/hyperledger-gerrit-archive/fabric-ca@v2.0.0-alpha.0.20190916143245-4cd4192f0366+incompatible/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  	"regexp"
    12  	"strings"
    13  
    14  	"github.com/cloudflare/cfssl/log"
    15  	"github.com/go-sql-driver/mysql"
    16  	"github.com/hyperledger/fabric-ca/lib/server/db"
    17  	"github.com/hyperledger/fabric-ca/lib/server/db/util"
    18  	"github.com/hyperledger/fabric-ca/lib/tls"
    19  	"github.com/hyperledger/fabric/bccsp"
    20  	"github.com/jmoiron/sqlx"
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  var (
    25  	re = regexp.MustCompile(`\/([0-9,a-z,A-Z$_]+)`)
    26  )
    27  
    28  // Mysql defines MySQL database
    29  type Mysql struct {
    30  	SqlxDB  db.FabricCADB
    31  	TLS     *tls.ClientTLSConfig
    32  	CSP     bccsp.BCCSP
    33  	CAName  string
    34  	Metrics *db.Metrics
    35  
    36  	datasource string
    37  	dbName     string
    38  }
    39  
    40  // NewDB create a MySQL database
    41  func NewDB(
    42  	datasource,
    43  	caName string,
    44  	clientTLSConfig *tls.ClientTLSConfig,
    45  	csp bccsp.BCCSP,
    46  	metrics *db.Metrics,
    47  ) *Mysql {
    48  	log.Debugf("Using MySQL database, connecting to database...")
    49  	return &Mysql{
    50  		TLS:        clientTLSConfig,
    51  		CSP:        csp,
    52  		datasource: datasource,
    53  		CAName:     caName,
    54  		Metrics:    metrics,
    55  	}
    56  }
    57  
    58  // Connect connects to a MySQL server
    59  func (m *Mysql) Connect() error {
    60  	datasource := m.datasource
    61  	clientTLSConfig := m.TLS
    62  
    63  	m.dbName = util.GetDBName(datasource)
    64  	log.Debugf("Database Name: %s", m.dbName)
    65  
    66  	connStr := re.ReplaceAllString(datasource, "/")
    67  
    68  	if clientTLSConfig.Enabled {
    69  		tlsConfig, err := tls.GetClientTLSConfig(clientTLSConfig, m.CSP)
    70  		if err != nil {
    71  			return errors.WithMessage(err, "Failed to get client TLS for MySQL")
    72  		}
    73  
    74  		mysql.RegisterTLSConfig("custom", tlsConfig)
    75  	}
    76  
    77  	log.Debugf("Connecting to MySQL server, using connection string: %s", util.MaskDBCred(connStr))
    78  	sqlxdb, err := sqlx.Connect("mysql", connStr)
    79  	if err != nil {
    80  		return errors.Wrap(err, "Failed to connect to MySQL database")
    81  	}
    82  
    83  	m.SqlxDB = db.New(sqlxdb, m.CAName, m.Metrics)
    84  	return nil
    85  }
    86  
    87  // PingContext pings the database
    88  func (m *Mysql) PingContext(ctx context.Context) error {
    89  	err := m.SqlxDB.PingContext(ctx)
    90  	if err != nil {
    91  		return errors.Wrap(err, "Failed to ping to MySQL database")
    92  	}
    93  	return nil
    94  }
    95  
    96  // Create creates database and tables
    97  func (m *Mysql) Create() (*db.DB, error) {
    98  	db, err := m.CreateDatabase()
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	err = m.CreateTables()
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	return db, nil
   107  }
   108  
   109  // CreateDatabase creates database
   110  func (m *Mysql) CreateDatabase() (*db.DB, error) {
   111  	datasource := m.datasource
   112  	dbName := m.dbName
   113  	err := m.createDatabase()
   114  	if err != nil {
   115  		return nil, errors.Wrap(err, "Failed to create MySQL database")
   116  	}
   117  
   118  	log.Debugf("Connecting to database '%s', using connection string: '%s'", dbName, util.MaskDBCred(datasource))
   119  	sqlxdb, err := sqlx.Open("mysql", datasource)
   120  	if err != nil {
   121  		return nil, errors.Wrapf(err, "Failed to open database (%s) in MySQL server", dbName)
   122  	}
   123  
   124  	m.SqlxDB = db.New(sqlxdb, m.CAName, m.Metrics)
   125  
   126  	return m.SqlxDB.(*db.DB), nil
   127  }
   128  
   129  // CreateTables creates table
   130  func (m *Mysql) CreateTables() error {
   131  	err := m.createTables()
   132  	if err != nil {
   133  		return errors.Wrap(err, "Failed to create MySQL tables")
   134  	}
   135  	return nil
   136  }
   137  
   138  func (m *Mysql) createDatabase() error {
   139  	dbName := m.dbName
   140  	log.Debugf("Creating MySQL Database (%s) if it does not exist...", dbName)
   141  
   142  	_, err := m.SqlxDB.Exec("CreateDatabase", "CREATE DATABASE IF NOT EXISTS "+dbName)
   143  	if err != nil {
   144  		return errors.Wrap(err, "Failed to execute create database query")
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  func (m *Mysql) createTables() error {
   151  	db := m.SqlxDB
   152  	log.Debug("Creating users table if it doesn't exist")
   153  	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 {
   154  		return errors.Wrap(err, "Error creating users table")
   155  	}
   156  	log.Debug("Creating affiliations table if it doesn't exist")
   157  	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 {
   158  		return errors.Wrap(err, "Error creating affiliations table")
   159  	}
   160  	log.Debug("Creating index on 'name' in the affiliations table")
   161  	if _, err := db.Exec("CreateAffiliationsIndex", "CREATE INDEX name_index on affiliations (name)"); err != nil {
   162  		if !strings.Contains(err.Error(), "Error 1061") { // Error 1061: Duplicate key name, index already exists
   163  			return errors.Wrap(err, "Error creating index on affiliations table")
   164  		}
   165  	}
   166  	log.Debug("Creating certificates table if it doesn't exist")
   167  	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(4096) NOT NULL, level INTEGER DEFAULT 0, PRIMARY KEY(serial_number, authority_key_identifier)) DEFAULT CHARSET=utf8 COLLATE utf8_bin"); err != nil {
   168  		return errors.Wrap(err, "Error creating certificates table")
   169  	}
   170  	log.Debug("Creating credentials table if it doesn't exist")
   171  	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 {
   172  		return errors.Wrap(err, "Error creating credentials table")
   173  	}
   174  	log.Debug("Creating revocation_authority_info table if it does not exist")
   175  	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 {
   176  		return errors.Wrap(err, "Error creating revocation_authority_info table")
   177  	}
   178  	log.Debug("Creating nonces table if it does not exist")
   179  	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 {
   180  		return errors.Wrap(err, "Error creating nonces table")
   181  	}
   182  	log.Debug("Creating properties table if it does not exist")
   183  	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 {
   184  		return errors.Wrap(err, "Error creating properties table")
   185  	}
   186  	_, 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')"))
   187  	if err != nil {
   188  		if !strings.Contains(err.Error(), "1062") { // MySQL error code for duplicate entry
   189  			return err
   190  		}
   191  	}
   192  	return nil
   193  }