Skip to content

Commit

Permalink
add mysql support for golang components (cloudfoundry#555)
Browse files Browse the repository at this point in the history
* add mysql support for golang components

* fix typo e to err

* update db changelog file with mysql

* fix .travis.yml and add comment to Connection function

* fix integration test

* fix err in integration test

* remove dbms from scalingengine.db.changelog.yml

* refine the helper.go and related test cases

* Format code and fix appmetrics empty error

* add transaction rollback
  • Loading branch information
aqan213 authored Mar 17, 2020
1 parent 06b4121 commit 8aef743
Show file tree
Hide file tree
Showing 40 changed files with 980 additions and 161 deletions.
5 changes: 4 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,7 @@
url = https://github.com/juju/ratelimit
[submodule "src/github.com/jmoiron/sqlx"]
path = src/github.com/jmoiron/sqlx
url = https://github.com/jmoiron/sqlx
url = https://github.com/jmoiron/sqlx
[submodule "src/github.com/go-sql-driver/mysql"]
path = src/github.com/go-sql-driver/mysql
url = https://github.com/go-sql-driver/mysql
15 changes: 14 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ dist: xenial

env:
global:
- DBURL=postgres://postgres@localhost/autoscaler?sslmode=disable
- NODE_VERSION=6.2
- GO_VERSION=1.13.3
- LOGLEVEL=info
Expand Down Expand Up @@ -41,21 +40,32 @@ before_script:
- sudo dpkg -i mysql-apt-config_0.8.14-1_all.deb
- sudo apt-get update -q
- sudo apt-get install -q -y --allow-unauthenticated -o Dpkg::Options::=--force-confnew mysql-server
- echo -e "[mysqld]\nsql_mode=NO_ENGINE_SUBSTITUTION,STRICT_ALL_TABLES\n[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=32MB" | sudo tee -a /etc/mysql/my.cnf
- sudo systemctl restart mysql
- sudo mysql_upgrade
- mysql --version
- mysql -u root -e "CREATE DATABASE autoscaler;"
- java -cp 'db/target/lib/*' liquibase.integration.commandline.Main --url jdbc:mysql://127.0.0.1/autoscaler --driver=com.mysql.cj.jdbc.Driver --changeLogFile=api/db/api.db.changelog.yml --username=root update
- java -cp 'db/target/lib/*' liquibase.integration.commandline.Main --url jdbc:mysql://127.0.0.1/autoscaler --driver=com.mysql.cj.jdbc.Driver --changeLogFile=servicebroker/db/servicebroker.db.changelog.json --username=root update
- java -cp 'db/target/lib/*' liquibase.integration.commandline.Main --url jdbc:mysql://127.0.0.1/autoscaler --driver=com.mysql.cj.jdbc.Driver --changeLogFile=scheduler/db/scheduler.changelog-master.yaml --username=root update
- java -cp 'db/target/lib/*' liquibase.integration.commandline.Main --url jdbc:mysql://127.0.0.1/autoscaler --driver=com.mysql.cj.jdbc.Driver --changeLogFile=scheduler/db/quartz.changelog-master.yaml --username=root update
- java -cp 'db/target/lib/*' liquibase.integration.commandline.Main --url jdbc:mysql://127.0.0.1/autoscaler --driver=com.mysql.cj.jdbc.Driver --changeLogFile=src/autoscaler/metricscollector/db/metricscollector.db.changelog.yml --username=root update
- java -cp 'db/target/lib/*' liquibase.integration.commandline.Main --url jdbc:mysql://127.0.0.1/autoscaler --driver=com.mysql.cj.jdbc.Driver --changeLogFile=src/autoscaler/eventgenerator/db/dataaggregator.db.changelog.yml --username=root update
- java -cp 'db/target/lib/*' liquibase.integration.commandline.Main --url jdbc:mysql://127.0.0.1/autoscaler --driver=com.mysql.cj.jdbc.Driver --changeLogFile=src/autoscaler/scalingengine/db/scalingengine.db.changelog.yml --username=root update
- java -cp 'db/target/lib/*' liquibase.integration.commandline.Main --url jdbc:mysql://127.0.0.1/autoscaler --driver=com.mysql.cj.jdbc.Driver --changeLogFile=src/autoscaler/operator/db/operator.db.changelog.yml --username=root update
jobs:
include:
- name: unit test
script:
# Unit test
- pushd src/autoscaler
- sudo cp /var/lib/mysql/ca.pem /tmp/ca.pem
- export DBURL="postgres://postgres@localhost/autoscaler?sslmode=disable"
- ginkgo -r -race -randomizeAllSpecs
- export DBURL="root@tcp(localhost)/autoscaler?tls=false"
- ginkgo -r -race -randomizeAllSpecs
- popd
- sudo rm /tmp/ca.pem
- pushd scheduler
- mvn test
- mvn test -Dspring.profiles.active=mysql
Expand All @@ -66,6 +76,9 @@ jobs:
- pushd scheduler
- mvn package -DskipTests
- popd
- export DBURL="postgres://postgres@localhost/autoscaler?sslmode=disable"
- ginkgo -r -race -randomizeAllSpecs src/integration
- export DBURL="root@tcp(localhost)/autoscaler?tls=false"
- ginkgo -r -race -randomizeAllSpecs src/integration

# Tests for legacy components (node apiserver, broker and metricscollector)
Expand Down
11 changes: 11 additions & 0 deletions scheduler/db/databasechangelog.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
databaseChangeLog:
- changeSet:
id: 1
author: aqan213
dbms: mysql
changes:
- addPrimaryKey:
columnNames: "id,author,filename"
constraintName: "PK_DATABASECHANGELOG"
schemaName: autoscaler
tableName: DATABASECHANGELOG
5 changes: 4 additions & 1 deletion src/autoscaler/api/cmd/api/api_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ var _ = SynchronizedBeforeSuite(func() []byte {
ap, err := gexec.Build("autoscaler/api/cmd/api", "-race")
Expect(err).NotTo(HaveOccurred())

apDB, err := sql.Open(db.PostgresDriverName, os.Getenv("DBURL"))
database, err := db.GetConnection(os.Getenv("DBURL"))
Expect(err).NotTo(HaveOccurred())

apDB, err := sql.Open(database.DriverName, database.DSN)
Expect(err).NotTo(HaveOccurred())

_, err = apDB.Exec("DELETE FROM binding")
Expand Down
1 change: 1 addition & 0 deletions src/autoscaler/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

const PostgresDriverName = "postgres"
const MysqlDriverName = "mysql"

type OrderType uint8

Expand Down
13 changes: 13 additions & 0 deletions src/autoscaler/db/db_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package db_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"testing"
)

func TestDb(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Db Suite")
}
148 changes: 148 additions & 0 deletions src/autoscaler/db/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package db

import (
"fmt"
"strings"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net/url"
"github.com/go-sql-driver/mysql"

)

type Database struct {
DriverName string
DSN string
}

type MySQLConfig struct {
config *mysql.Config
cert string
}

/**
This function is used to generate db connection info, for example,
For mysql:
input dbUrl: 'username:password@tcp(localhost:3306)/autoscaler?tls=custom&sslrootcert=db_ca.crt'
return:
&Database{DriverName: "mysql", DSN:"username:password@tcp(localhost:3306)/autoscaler?parseTime=true&tls=custom"}
For postgres:
input dbUrl: postgres://postgres:password@localhost:5432/autoscaler?sslmode=disable
return:
&Database{DriverName: "postgres", DSN:"postgres://postgres:password@localhost:5432/autoscaler?sslmode=disable"
**/
func GetConnection(dbUrl string) (*Database, error) {
database := &Database{}

database.DriverName = detectDirver(dbUrl)

switch database.DriverName {
case MysqlDriverName:
cfg, err := parseMySQLURL(dbUrl)
if err != nil {
return nil, err
}

err = registerConfig(cfg)
if err != nil {
return nil, err
}
database.DSN = cfg.config.FormatDSN()
case PostgresDriverName:
database.DSN = dbUrl
}
return database, nil
}

func registerConfig(cfg *MySQLConfig) error {
tlsValue := cfg.config.TLSConfig
if _, isBool := readBool(tlsValue); isBool || tlsValue == "" || strings.ToLower(tlsValue) == "skip-verify" || strings.ToLower(tlsValue) == "preferred" {
// Do nothing here
return nil
} else if cfg.cert != "" {
certBytes, err := ioutil.ReadFile(cfg.cert)
if err != nil {
return err
}
caCertPool := x509.NewCertPool()
if ok := caCertPool.AppendCertsFromPEM(certBytes); !ok {
return err
}

tlsConfig := tls.Config{}
tlsConfig.RootCAs = caCertPool
if tlsValue == "verify_identity" {
tlsConfig.ServerName = strings.Split(cfg.config.Addr,":")[0]
}

err = mysql.RegisterTLSConfig(tlsValue, &tlsConfig)
if err != nil {
return err
}

} else {
return fmt.Errorf("sql ca file is not provided")
}
return nil
}

func readBool(input string) (value bool, valid bool) {
switch input {
case "1", "true", "TRUE", "True":
return true, true
case "0", "false", "FALSE", "False":
return false, true
}
return
}

func detectDirver(dbUrl string)(driver string) {
if strings.Contains(dbUrl, "postgres"){
return PostgresDriverName
} else {
return MysqlDriverName
}
}

// parseMySQLURL can parse the query parameters and remove invalid 'sslrootcert', it return mysql.Config and the cert file.
func parseMySQLURL(dbUrl string)( *MySQLConfig, error) {
var caCert string
var tlsValue string
if strings.Contains(dbUrl,"?"){
u, err := url.ParseQuery(strings.Split(dbUrl,"?")[1])
if err != nil {
return nil, err
}
urlParam := url.Values{}
for k, v := range u {
if k == "sslrootcert" {
caCert = v[0]
continue
}
if k == "tls" {
tlsValue = v[0]
continue
}
urlParam.Add(k, v[0])
}
dbUrl = fmt.Sprintf("%s?%s",strings.Split(dbUrl,"?")[0], urlParam.Encode())
}

config, err := mysql.ParseDSN(dbUrl)
if err != nil {
return nil, err
}
config.ParseTime = true

if tlsValue != "" {
config.TLSConfig = tlsValue
}

return &MySQLConfig{
config: config,
cert: caCert,
}, nil
}

87 changes: 87 additions & 0 deletions src/autoscaler/db/helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package db_test

import (
. "autoscaler/db"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Helper", func() {
var (
dbUrl string
err error
database *Database
certPath string
)

Describe("GetConnection", func() {

JustBeforeEach(func() {
database, err = GetConnection(dbUrl)
})
Context("when mysql query parameters are provided", func() {
BeforeEach(func() {
dbUrl="root@tcp(localhost:3306)/autoscaler?tls=preferred"
})
It("returns mysql database object", func() {
Expect(err).NotTo(HaveOccurred())
Expect(database).To(Equal(&Database{
DriverName: "mysql",
DSN: "root@tcp(localhost:3306)/autoscaler?parseTime=true&tls=preferred",
}))
})
})

Context("when mysql query parameters are not provided", func() {
BeforeEach(func() {
dbUrl="root@tcp(localhost:3306)/autoscaler"
})
It("returns mysql database object", func() {
Expect(err).NotTo(HaveOccurred())
Expect(database).To(Equal(&Database{
DriverName: "mysql",
DSN: "root@tcp(localhost:3306)/autoscaler?parseTime=true",
}))
})

})

Context("when need to verify mysql server, cert is provided ", func() {
BeforeEach(func() {
certPath="../../../test-certs/api.crt"
dbUrl="root@tcp(localhost:3306)/autoscaler?tls=verify-ca&sslrootcert="+certPath
})
It("returns mysql database connection", func() {
Expect(err).NotTo(HaveOccurred())
Expect(database).To(Equal(&Database{
DriverName: "mysql",
DSN: "root@tcp(localhost:3306)/autoscaler?parseTime=true&tls=verify-ca",
}))
})
})

Context("when need to verify mysql server, cert is not provided ", func() {
BeforeEach(func() {
dbUrl="root@tcp(localhost:3306)/autoscaler?tls=verify-ca"
})
It("should error", func() {
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql ca file is not provided"))
})
})

Context("when postgres dburl is provided", func() {
BeforeEach(func() {
dbUrl="postgres://postgres:password@localhost:5432/autoscaler?sslmode=disable"
})
It("returns postgres database object", func() {
Expect(err).NotTo(HaveOccurred())
Expect(database).To(Equal(&Database{
DriverName: "postgres",
DSN: "postgres://postgres:password@localhost:5432/autoscaler?sslmode=disable",
}))
})
})
})
})
Loading

0 comments on commit 8aef743

Please sign in to comment.