diff --git a/internal/tls/config.go b/internal/tls/config.go new file mode 100644 index 0000000000000..ce79583430d3e --- /dev/null +++ b/internal/tls/config.go @@ -0,0 +1,130 @@ +package tls + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" +) + +// ClientConfig represents the standard client TLS config. +type ClientConfig struct { + TLSCA string `toml:"tls_ca"` + TLSCert string `toml:"tls_cert"` + TLSKey string `toml:"tls_key"` + InsecureSkipVerify bool `toml:"insecure_skip_verify"` + + // Deprecated in 1.7; use TLS variables above + SSLCA string `toml:"ssl_ca"` + SSLCert string `toml:"ssl_cert"` + SSLKey string `toml:"ssl_key"` +} + +// ServerConfig represents the standard server TLS config. +type ServerConfig struct { + TLSCert string `toml:"tls_cert"` + TLSKey string `toml:"tls_key"` + TLSAllowedCACerts []string `toml:"tls_allowed_cacerts"` +} + +// TLSConfig returns a tls.Config, may be nil without error if TLS is not +// configured. +func (c *ClientConfig) TLSConfig() (*tls.Config, error) { + // Support deprecated variable names + if c.TLSCA == "" && c.SSLCA != "" { + c.TLSCA = c.SSLCA + } + if c.TLSCert == "" && c.SSLCert != "" { + c.TLSCert = c.SSLCert + } + if c.TLSKey == "" && c.SSLKey != "" { + c.TLSKey = c.SSLKey + } + + // TODO: return default tls.Config; plugins should not call if they don't + // want TLS, this will require using another option to determine. In the + // case of an HTTP plugin, you could use `https`. Other plugins may need + // the dedicated option `TLSEnable`. + if c.TLSCA == "" && c.TLSKey == "" && c.TLSCert == "" && !c.InsecureSkipVerify { + return nil, nil + } + + tlsConfig := &tls.Config{ + InsecureSkipVerify: c.InsecureSkipVerify, + Renegotiation: tls.RenegotiateNever, + } + + if c.TLSCA != "" { + pool, err := makeCertPool([]string{c.TLSCA}) + if err != nil { + return nil, err + } + tlsConfig.RootCAs = pool + } + + if c.TLSCert != "" && c.TLSKey != "" { + err := loadCertificate(tlsConfig, c.TLSCert, c.TLSKey) + if err != nil { + return nil, err + } + } + + return tlsConfig, nil +} + +// TLSConfig returns a tls.Config, may be nil without error if TLS is not +// configured. +func (c *ServerConfig) TLSConfig() (*tls.Config, error) { + if c.TLSCert == "" && c.TLSKey == "" && len(c.TLSAllowedCACerts) == 0 { + return nil, nil + } + + tlsConfig := &tls.Config{} + + if len(c.TLSAllowedCACerts) != 0 { + pool, err := makeCertPool(c.TLSAllowedCACerts) + if err != nil { + return nil, err + } + tlsConfig.ClientCAs = pool + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + + if c.TLSCert != "" && c.TLSKey != "" { + err := loadCertificate(tlsConfig, c.TLSCert, c.TLSKey) + if err != nil { + return nil, err + } + } + + return tlsConfig, nil +} + +func makeCertPool(certFiles []string) (*x509.CertPool, error) { + pool := x509.NewCertPool() + for _, certFile := range certFiles { + pem, err := ioutil.ReadFile(certFile) + if err != nil { + return nil, fmt.Errorf( + "could not read certificate %q: %v", certFile, err) + } + ok := pool.AppendCertsFromPEM(pem) + if !ok { + return nil, fmt.Errorf( + "could not parse any PEM certificates %q: %v", certFile, err) + } + } + return pool, nil +} + +func loadCertificate(config *tls.Config, certFile, keyFile string) error { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return fmt.Errorf( + "could not load keypair %s:%s: %v", certFile, keyFile, err) + } + + config.Certificates = []tls.Certificate{cert} + config.BuildNameToCertificate() + return nil +} diff --git a/internal/tls/config_test.go b/internal/tls/config_test.go new file mode 100644 index 0000000000000..31a70d9a18ebd --- /dev/null +++ b/internal/tls/config_test.go @@ -0,0 +1,226 @@ +package tls_test + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/influxdata/telegraf/internal/tls" + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/require" +) + +var pki = testutil.NewPKI("../../testutil/pki") + +func TestClientConfig(t *testing.T) { + tests := []struct { + name string + client tls.ClientConfig + expNil bool + expErr bool + }{ + { + name: "unset", + client: tls.ClientConfig{}, + expNil: true, + }, + { + name: "success", + client: tls.ClientConfig{ + TLSCA: pki.CACertPath(), + TLSCert: pki.ClientCertPath(), + TLSKey: pki.ClientKeyPath(), + }, + }, + { + name: "invalid ca", + client: tls.ClientConfig{ + TLSCA: pki.ClientKeyPath(), + TLSCert: pki.ClientCertPath(), + TLSKey: pki.ClientKeyPath(), + }, + expNil: true, + expErr: true, + }, + { + name: "missing ca is okay", + client: tls.ClientConfig{ + TLSCert: pki.ClientCertPath(), + TLSKey: pki.ClientKeyPath(), + }, + }, + { + name: "invalid cert", + client: tls.ClientConfig{ + TLSCA: pki.CACertPath(), + TLSCert: pki.ClientKeyPath(), + TLSKey: pki.ClientKeyPath(), + }, + expNil: true, + expErr: true, + }, + { + name: "missing cert skips client keypair", + client: tls.ClientConfig{ + TLSCA: pki.CACertPath(), + TLSKey: pki.ClientKeyPath(), + }, + expNil: false, + expErr: false, + }, + { + name: "missing key skips client keypair", + client: tls.ClientConfig{ + TLSCA: pki.CACertPath(), + TLSCert: pki.ClientCertPath(), + }, + expNil: false, + expErr: false, + }, + { + name: "support deprecated ssl field names", + client: tls.ClientConfig{ + SSLCA: pki.CACertPath(), + SSLCert: pki.ClientCertPath(), + SSLKey: pki.ClientKeyPath(), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tlsConfig, err := tt.client.TLSConfig() + if !tt.expNil { + require.NotNil(t, tlsConfig) + } else { + require.Nil(t, tlsConfig) + } + + if !tt.expErr { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} + +func TestServerConfig(t *testing.T) { + tests := []struct { + name string + server tls.ServerConfig + expNil bool + expErr bool + }{ + { + name: "unset", + server: tls.ServerConfig{}, + expNil: true, + }, + { + name: "success", + server: tls.ServerConfig{ + TLSCert: pki.ServerCertPath(), + TLSKey: pki.ServerKeyPath(), + TLSAllowedCACerts: []string{pki.CACertPath()}, + }, + }, + { + name: "invalid ca", + server: tls.ServerConfig{ + TLSCert: pki.ServerCertPath(), + TLSKey: pki.ServerKeyPath(), + TLSAllowedCACerts: []string{pki.ServerKeyPath()}, + }, + expNil: true, + expErr: true, + }, + { + name: "missing allowed ca is okay", + server: tls.ServerConfig{ + TLSCert: pki.ServerCertPath(), + TLSKey: pki.ServerKeyPath(), + }, + expNil: true, + expErr: true, + }, + { + name: "invalid cert", + server: tls.ServerConfig{ + TLSCert: pki.ServerKeyPath(), + TLSKey: pki.ServerKeyPath(), + TLSAllowedCACerts: []string{pki.CACertPath()}, + }, + expNil: true, + expErr: true, + }, + { + name: "missing cert", + server: tls.ServerConfig{ + TLSKey: pki.ServerKeyPath(), + TLSAllowedCACerts: []string{pki.CACertPath()}, + }, + expNil: true, + expErr: true, + }, + { + name: "missing key", + server: tls.ServerConfig{ + TLSCert: pki.ServerCertPath(), + TLSAllowedCACerts: []string{pki.CACertPath()}, + }, + expNil: true, + expErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tlsConfig, err := tt.server.TLSConfig() + if !tt.expNil { + require.NotNil(t, tlsConfig) + } + if !tt.expErr { + require.NoError(t, err) + } + }) + } +} + +func TestConnect(t *testing.T) { + clientConfig := tls.ClientConfig{ + TLSCA: pki.CACertPath(), + TLSCert: pki.ClientCertPath(), + TLSKey: pki.ClientKeyPath(), + } + + serverConfig := tls.ServerConfig{ + TLSCert: pki.ServerCertPath(), + TLSKey: pki.ServerKeyPath(), + TLSAllowedCACerts: []string{pki.CACertPath()}, + } + + serverTLSConfig, err := serverConfig.TLSConfig() + require.NoError(t, err) + + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + ts.TLS = serverTLSConfig + + ts.StartTLS() + defer ts.Close() + + clientTLSConfig, err := clientConfig.TLSConfig() + require.NoError(t, err) + + client := http.Client{ + Transport: &http.Transport{ + TLSClientConfig: clientTLSConfig, + }, + Timeout: 10 * time.Second, + } + + resp, err := client.Get(ts.URL) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) +} diff --git a/plugins/inputs/mysql/README.md b/plugins/inputs/mysql/README.md index fb9a18eaba9a7..564d75e614046 100644 --- a/plugins/inputs/mysql/README.md +++ b/plugins/inputs/mysql/README.md @@ -1,4 +1,4 @@ -# MySQL Input plugin +# MySQL Input Plugin This plugin gathers the statistic data from MySQL server @@ -18,9 +18,9 @@ This plugin gathers the statistic data from MySQL server * File events statistics * Table schema statistics -## Configuration +### Configuration -``` +```toml # Read metrics from one or many mysql servers [[inputs.mysql]] ## specify servers via a url matching: @@ -81,14 +81,97 @@ This plugin gathers the statistic data from MySQL server # ## Some queries we may want to run less often (such as SHOW GLOBAL VARIABLES) interval_slow = "30m" - - ## Optional SSL Config (will be used if tls=custom parameter specified in server uri) - ssl_ca = "/etc/telegraf/ca.pem" - ssl_cert = "/etc/telegraf/cert.pem" - ssl_key = "/etc/telegraf/key.pem" + + ## Optional TLS Config (will be used if tls=custom parameter specified in server uri) + tls_ca = "/etc/telegraf/ca.pem" + tls_cert = "/etc/telegraf/cert.pem" + tls_key = "/etc/telegraf/key.pem" +``` + +#### Metric Version + +When `metric_version = 2`, a variety of field type issues are corrected as well +as naming inconsistencies. If you have existing data on the original version +enabling this feature will cause a `field type error` when inserted into +InfluxDB due to the change of types. For this reason, you should keep the +`metric_version` unset until you are ready to migrate to the new format. + +If preserving your old data is not required you may wish to drop conflicting +measurements: +``` +DROP SERIES from mysql +DROP SERIES from mysql_variables +DROP SERIES from mysql_innodb ``` -## Measurements & Fields +Otherwise, migration can be performed using the following steps: + +1. Duplicate your `mysql` plugin configuration and add a `name_suffix` and +`metric_version = 2`, this will result in collection using both the old and new +style concurrently: + ```toml + [[inputs.mysql]] + servers = ["tcp(127.0.0.1:3306)/"] + + [[inputs.mysql]] + name_suffix = "_v2" + metric_version = 2 + + servers = ["tcp(127.0.0.1:3306)/"] + ``` + +2. Upgrade all affected Telegraf clients to version >=1.6. + + New measurements will be created with the `name_suffix`, for example:: + - `mysql_v2` + - `mysql_variables_v2` + +3. Update charts, alerts, and other supporting code to the new format. +4. You can now remove the old `mysql` plugin configuration and remove old + measurements. + +If you wish to remove the `name_suffix` you may use Kapacitor to copy the +historical data to the default name. Do this only after retiring the old +measurement name. + +1. Use the techinique described above to write to multiple locations: + ```toml + [[inputs.mysql]] + servers = ["tcp(127.0.0.1:3306)/"] + metric_version = 2 + + [[inputs.mysql]] + name_suffix = "_v2" + metric_version = 2 + + servers = ["tcp(127.0.0.1:3306)/"] + ``` +2. Create a TICKScript to copy the historical data: + ``` + dbrp "telegraf"."autogen" + + batch + |query(''' + SELECT * FROM "telegraf"."autogen"."mysql_v2" + ''') + .period(5m) + .every(5m) + |influxDBOut() + .database('telegraf') + .retentionPolicy('autogen') + .measurement('mysql') + ``` +3. Define a task for your script: + ```sh + kapacitor define copy-measurement -tick copy-measurement.task + ``` +4. Run the task over the data you would like to migrate: + ```sh + kapacitor replay-live batch -start 2018-03-30T20:00:00Z -stop 2018-04-01T12:00:00Z -rec-time -task copy-measurement + ``` +5. Verify copied data and repeat for other measurements. + +### Metrics: * Global statuses - all numeric and boolean values of `SHOW GLOBAL STATUSES` * Global variables - all numeric and boolean values of `SHOW GLOBAL VARIABLES` * Slave status - metrics from `SHOW SLAVE STATUS` the metrics are gathered when diff --git a/plugins/inputs/mysql/mysql.go b/plugins/inputs/mysql/mysql.go index c3dc38423a7fa..87848cf1341ac 100644 --- a/plugins/inputs/mysql/mysql.go +++ b/plugins/inputs/mysql/mysql.go @@ -4,15 +4,15 @@ import ( "bytes" "database/sql" "fmt" - "log" "strconv" "strings" "sync" "time" "github.com/influxdata/telegraf" - "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/internal/tls" "github.com/influxdata/telegraf/plugins/inputs" + "github.com/influxdata/telegraf/plugins/inputs/mysql/v1" "github.com/go-sql-driver/mysql" ) @@ -37,9 +37,8 @@ type Mysql struct { GatherFileEventsStats bool `toml:"gather_file_events_stats"` GatherPerfEventsStatements bool `toml:"gather_perf_events_statements"` IntervalSlow string `toml:"interval_slow"` - SSLCA string `toml:"ssl_ca"` - SSLCert string `toml:"ssl_cert"` - SSLKey string `toml:"ssl_key"` + MetricVersion int `toml:"metric_version"` + tls.ClientConfig } var sampleConfig = ` @@ -52,6 +51,20 @@ var sampleConfig = ` # ## If no servers are specified, then localhost is used as the host. servers = ["tcp(127.0.0.1:3306)/"] + + ## Selects the metric output format. + ## + ## This option exists to maintain backwards compatibility, if you have + ## existing metrics do not set or change this value until you are ready to + ## migrate to the new format. + ## + ## If you do not have existing metrics from this plugin set to the latest + ## version. + ## + ## Telegraf >=1.6: metric_version = 2 + ## <1.6: metric_version = 1 (or unset) + metric_version = 2 + ## the limits for metrics form perf_events_statements perf_events_statements_digest_text_limit = 120 perf_events_statements_limit = 250 @@ -66,7 +79,7 @@ var sampleConfig = ` ## gather thread state counts from INFORMATION_SCHEMA.PROCESSLIST gather_process_list = true # - ## gather thread state counts from INFORMATION_SCHEMA.USER_STATISTICS + ## gather user statistics from INFORMATION_SCHEMA.USER_STATISTICS gather_user_statistics = true # ## gather auto_increment columns and max values from information schema @@ -102,10 +115,12 @@ var sampleConfig = ` ## Some queries we may want to run less often (such as SHOW GLOBAL VARIABLES) interval_slow = "30m" - ## Optional SSL Config (will be used if tls=custom parameter specified in server uri) - ssl_ca = "/etc/telegraf/ca.pem" - ssl_cert = "/etc/telegraf/cert.pem" - ssl_key = "/etc/telegraf/key.pem" + ## Optional TLS Config (will be used if tls=custom parameter specified in server uri) + # tls_ca = "/etc/telegraf/ca.pem" + # tls_cert = "/etc/telegraf/cert.pem" + # tls_key = "/etc/telegraf/key.pem" + ## Use TLS but skip chain & host verification + # insecure_skip_verify = false ` var defaultTimeout = time.Second * time.Duration(5) @@ -145,9 +160,9 @@ func (m *Mysql) Gather(acc telegraf.Accumulator) error { m.InitMysql() } - tlsConfig, err := internal.GetTLSConfig(m.SSLCert, m.SSLKey, m.SSLCA, false) + tlsConfig, err := m.ClientConfig.TLSConfig() if err != nil { - log.Printf("E! MySQL Error registering TLS config: %s", err) + return fmt.Errorf("registering TLS config: %s", err) } if tlsConfig != nil { @@ -169,182 +184,6 @@ func (m *Mysql) Gather(acc telegraf.Accumulator) error { return nil } -type mapping struct { - onServer string - inExport string -} - -var mappings = []*mapping{ - { - onServer: "Aborted_", - inExport: "aborted_", - }, - { - onServer: "Bytes_", - inExport: "bytes_", - }, - { - onServer: "Com_", - inExport: "commands_", - }, - { - onServer: "Created_", - inExport: "created_", - }, - { - onServer: "Handler_", - inExport: "handler_", - }, - { - onServer: "Innodb_", - inExport: "innodb_", - }, - { - onServer: "Key_", - inExport: "key_", - }, - { - onServer: "Open_", - inExport: "open_", - }, - { - onServer: "Opened_", - inExport: "opened_", - }, - { - onServer: "Qcache_", - inExport: "qcache_", - }, - { - onServer: "Table_", - inExport: "table_", - }, - { - onServer: "Tokudb_", - inExport: "tokudb_", - }, - { - onServer: "Threads_", - inExport: "threads_", - }, - { - onServer: "Access_", - inExport: "access_", - }, - { - onServer: "Aria__", - inExport: "aria_", - }, - { - onServer: "Binlog__", - inExport: "binlog_", - }, - { - onServer: "Busy_", - inExport: "busy_", - }, - { - onServer: "Connection_", - inExport: "connection_", - }, - { - onServer: "Delayed_", - inExport: "delayed_", - }, - { - onServer: "Empty_", - inExport: "empty_", - }, - { - onServer: "Executed_", - inExport: "executed_", - }, - { - onServer: "Executed_", - inExport: "executed_", - }, - { - onServer: "Feature_", - inExport: "feature_", - }, - { - onServer: "Flush_", - inExport: "flush_", - }, - { - onServer: "Last_", - inExport: "last_", - }, - { - onServer: "Master_", - inExport: "master_", - }, - { - onServer: "Max_", - inExport: "max_", - }, - { - onServer: "Memory_", - inExport: "memory_", - }, - { - onServer: "Not_", - inExport: "not_", - }, - { - onServer: "Performance_", - inExport: "performance_", - }, - { - onServer: "Prepared_", - inExport: "prepared_", - }, - { - onServer: "Rows_", - inExport: "rows_", - }, - { - onServer: "Rpl_", - inExport: "rpl_", - }, - { - onServer: "Select_", - inExport: "select_", - }, - { - onServer: "Slave_", - inExport: "slave_", - }, - { - onServer: "Slow_", - inExport: "slow_", - }, - { - onServer: "Sort_", - inExport: "sort_", - }, - { - onServer: "Subquery_", - inExport: "subquery_", - }, - { - onServer: "Tc_", - inExport: "tc_", - }, - { - onServer: "Threadpool_", - inExport: "threadpool_", - }, - { - onServer: "wsrep_", - inExport: "wsrep_", - }, - { - onServer: "Uptime_", - inExport: "uptime_", - }, -} - var ( // status counter generalThreadStates = map[string]uint32{ @@ -363,10 +202,10 @@ var ( "deleting": uint32(0), "executing": uint32(0), "execution of init_command": uint32(0), - "end": uint32(0), - "freeing items": uint32(0), - "flushing tables": uint32(0), - "fulltext initialization": uint32(0), + "end": uint32(0), + "freeing items": uint32(0), + "flushing tables": uint32(0), + "fulltext initialization": uint32(0), "idle": uint32(0), "init": uint32(0), "killed": uint32(0), @@ -402,8 +241,8 @@ var ( } // plaintext statuses stateStatusMappings = map[string]string{ - "user sleep": "idle", - "creating index": "altering table", + "user sleep": "idle", + "creating index": "altering table", "committing alter table to storage engine": "altering table", "discard or import tablespace": "altering table", "rename": "altering table", @@ -442,9 +281,8 @@ const ( GROUP BY command,state ORDER BY null` infoSchemaUserStatisticsQuery = ` - SELECT *,count(*) - FROM information_schema.user_statistics - GROUP BY user` + SELECT * + FROM information_schema.user_statistics` infoSchemaAutoIncQuery = ` SELECT table_schema, table_name, column_name, auto_increment, CAST(pow(2, case data_type @@ -717,9 +555,8 @@ func (m *Mysql) gatherGlobalVariables(db *sql.DB, serv string, acc telegraf.Accu fields[key] = string(val) tags[key] = string(val) } - // parse value, if it is numeric then save, otherwise ignore - if floatVal, ok := parseValue(val); ok { - fields[key] = floatVal + if value, ok := m.parseValue(val); ok { + fields[key] = value } // Send 20 fields at a time if len(fields) >= 20 { @@ -769,8 +606,10 @@ func (m *Mysql) gatherSlaveStatuses(db *sql.DB, serv string, acc telegraf.Accumu } // range over columns, and try to parse values for i, col := range cols { - // skip unparsable values - if value, ok := parseValue(*vals[i].(*sql.RawBytes)); ok { + if m.MetricVersion >= 2 { + col = strings.ToLower(col) + } + if value, ok := m.parseValue(*vals[i].(*sql.RawBytes)); ok { fields["slave_"+col] = value } } @@ -820,199 +659,105 @@ func (m *Mysql) gatherBinaryLogs(db *sql.DB, serv string, acc telegraf.Accumulat // the mappings of actual names and names of each status to be exported // to output is provided on mappings variable func (m *Mysql) gatherGlobalStatuses(db *sql.DB, serv string, acc telegraf.Accumulator) error { - // If user forgot the '/', add it - if strings.HasSuffix(serv, ")") { - serv = serv + "/" - } else if serv == "localhost" { - serv = "" - } - // run query rows, err := db.Query(globalStatusQuery) if err != nil { return err } + defer rows.Close() // parse the DSN and save host name as a tag servtag := getDSNTag(serv) tags := map[string]string{"server": servtag} fields := make(map[string]interface{}) for rows.Next() { - var name string - var val interface{} + var key string + var val sql.RawBytes - err = rows.Scan(&name, &val) - if err != nil { + if err = rows.Scan(&key, &val); err != nil { return err } - var found bool - - // iterate over mappings and gather metrics that is provided on mapping - for _, mapped := range mappings { - if strings.HasPrefix(name, mapped.onServer) { - // convert numeric values to integer - i, _ := strconv.Atoi(string(val.([]byte))) - fields[mapped.inExport+name[len(mapped.onServer):]] = i - found = true - } - } - // Send 20 fields at a time - if len(fields) >= 20 { - acc.AddFields("mysql", fields, tags) - fields = make(map[string]interface{}) - } - - if found { - continue - } - - // search for specific values - switch name { - case "Queries": - i, err := strconv.ParseInt(string(val.([]byte)), 10, 64) - if err != nil { - acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", name, err)) - } else { - fields["queries"] = i - } - case "Questions": - i, err := strconv.ParseInt(string(val.([]byte)), 10, 64) - if err != nil { - acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", name, err)) - } else { - fields["questions"] = i + if m.MetricVersion < 2 { + var found bool + for _, mapped := range v1.Mappings { + if strings.HasPrefix(key, mapped.OnServer) { + // convert numeric values to integer + i, _ := strconv.Atoi(string(val)) + fields[mapped.InExport+key[len(mapped.OnServer):]] = i + found = true + } } - case "Slow_queries": - i, err := strconv.ParseInt(string(val.([]byte)), 10, 64) - if err != nil { - acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", name, err)) - } else { - fields["slow_queries"] = i + // Send 20 fields at a time + if len(fields) >= 20 { + acc.AddFields("mysql", fields, tags) + fields = make(map[string]interface{}) } - case "Connections": - i, err := strconv.ParseInt(string(val.([]byte)), 10, 64) - if err != nil { - acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", name, err)) - } else { - fields["connections"] = i + if found { + continue } - case "Syncs": - i, err := strconv.ParseInt(string(val.([]byte)), 10, 64) - if err != nil { - acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", name, err)) - } else { - fields["syncs"] = i - } - case "Uptime": - i, err := strconv.ParseInt(string(val.([]byte)), 10, 64) - if err != nil { - acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", name, err)) - } else { - fields["uptime"] = i - } - } - } - // Send any remaining fields - if len(fields) > 0 { - acc.AddFields("mysql", fields, tags) - } - // gather connection metrics from processlist for each user - if m.GatherProcessList { - conn_rows, err := db.Query("SELECT user, sum(1) FROM INFORMATION_SCHEMA.PROCESSLIST GROUP BY user") - if err != nil { - log.Printf("E! MySQL Error gathering process list: %s", err) - } else { - for conn_rows.Next() { - var user string - var connections int64 - err = conn_rows.Scan(&user, &connections) + // search for specific values + switch key { + case "Queries": + i, err := strconv.ParseInt(string(val), 10, 64) if err != nil { - return err + acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", key, err)) + } else { + fields["queries"] = i } - - tags := map[string]string{"server": servtag, "user": user} - fields := make(map[string]interface{}) - + case "Questions": + i, err := strconv.ParseInt(string(val), 10, 64) if err != nil { - return err + acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", key, err)) + } else { + fields["questions"] = i } - fields["connections"] = connections - acc.AddFields("mysql_users", fields, tags) - } - } - } - - // gather connection metrics from user_statistics for each user - if m.GatherUserStatistics { - conn_rows, err := db.Query("select user, total_connections, concurrent_connections, connected_time, busy_time, cpu_time, bytes_received, bytes_sent, binlog_bytes_written, rows_fetched, rows_updated, table_rows_read, select_commands, update_commands, other_commands, commit_transactions, rollback_transactions, denied_connections, lost_connections, access_denied, empty_queries, total_ssl_connections FROM INFORMATION_SCHEMA.USER_STATISTICS GROUP BY user") - if err != nil { - log.Printf("E! MySQL Error gathering user stats: %s", err) - } else { - for conn_rows.Next() { - var user string - var total_connections int64 - var concurrent_connections int64 - var connected_time int64 - var busy_time int64 - var cpu_time int64 - var bytes_received int64 - var bytes_sent int64 - var binlog_bytes_written int64 - var rows_fetched int64 - var rows_updated int64 - var table_rows_read int64 - var select_commands int64 - var update_commands int64 - var other_commands int64 - var commit_transactions int64 - var rollback_transactions int64 - var denied_connections int64 - var lost_connections int64 - var access_denied int64 - var empty_queries int64 - var total_ssl_connections int64 - - err = conn_rows.Scan(&user, &total_connections, &concurrent_connections, - &connected_time, &busy_time, &cpu_time, &bytes_received, &bytes_sent, &binlog_bytes_written, - &rows_fetched, &rows_updated, &table_rows_read, &select_commands, &update_commands, &other_commands, - &commit_transactions, &rollback_transactions, &denied_connections, &lost_connections, &access_denied, - &empty_queries, &total_ssl_connections, - ) - + case "Slow_queries": + i, err := strconv.ParseInt(string(val), 10, 64) if err != nil { - return err + acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", key, err)) + } else { + fields["slow_queries"] = i } - - tags := map[string]string{"server": servtag, "user": user} - fields := map[string]interface{}{ - "total_connections": total_connections, - "concurrent_connections": concurrent_connections, - "connected_time": connected_time, - "busy_time": busy_time, - "cpu_time": cpu_time, - "bytes_received": bytes_received, - "bytes_sent": bytes_sent, - "binlog_bytes_written": binlog_bytes_written, - "rows_fetched": rows_fetched, - "rows_updated": rows_updated, - "table_rows_read": table_rows_read, - "select_commands": select_commands, - "update_commands": update_commands, - "other_commands": other_commands, - "commit_transactions": commit_transactions, - "rollback_transactions": rollback_transactions, - "denied_connections": denied_connections, - "lost_connections": lost_connections, - "access_denied": access_denied, - "empty_queries": empty_queries, - "total_ssl_connections": total_ssl_connections, + case "Connections": + i, err := strconv.ParseInt(string(val), 10, 64) + if err != nil { + acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", key, err)) + } else { + fields["connections"] = i } - - acc.AddFields("mysql_user_stats", fields, tags) + case "Syncs": + i, err := strconv.ParseInt(string(val), 10, 64) + if err != nil { + acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", key, err)) + } else { + fields["syncs"] = i + } + case "Uptime": + i, err := strconv.ParseInt(string(val), 10, 64) + if err != nil { + acc.AddError(fmt.Errorf("E! Error mysql: parsing %s int value (%s)", key, err)) + } else { + fields["uptime"] = i + } + } + } else { + key = strings.ToLower(key) + if value, ok := m.parseValue(val); ok { + fields[key] = value } } + + // Send 20 fields at a time + if len(fields) >= 20 { + acc.AddFields("mysql", fields, tags) + fields = make(map[string]interface{}) + } + } + // Send any remaining fields + if len(fields) > 0 { + acc.AddFields("mysql", fields, tags) } return nil @@ -1059,7 +804,34 @@ func (m *Mysql) GatherProcessListStatuses(db *sql.DB, serv string, acc telegraf. for s, c := range stateCounts { fields[newNamespace("threads", s)] = c } - acc.AddFields("mysql_info_schema", fields, tags) + if m.MetricVersion < 2 { + acc.AddFields("mysql_info_schema", fields, tags) + } else { + acc.AddFields("mysql_process_list", fields, tags) + } + + // get count of connections from each user + conn_rows, err := db.Query("SELECT user, sum(1) AS connections FROM INFORMATION_SCHEMA.PROCESSLIST GROUP BY user") + if err != nil { + return err + } + + for conn_rows.Next() { + var user string + var connections int64 + + err = conn_rows.Scan(&user, &connections) + if err != nil { + return err + } + + tags := map[string]string{"server": servtag, "user": user} + fields := make(map[string]interface{}) + + fields["connections"] = connections + acc.AddFields("mysql_users", fields, tags) + } + return nil } @@ -1069,77 +841,190 @@ func (m *Mysql) GatherUserStatisticsStatuses(db *sql.DB, serv string, acc telegr // run query rows, err := db.Query(infoSchemaUserStatisticsQuery) if err != nil { + // disable collecting if table is not found (mysql specific error) + // (suppresses repeat errors) + if strings.Contains(err.Error(), "nknown table 'user_statistics'") { + m.GatherUserStatistics = false + } return err } defer rows.Close() - var ( - user string - total_connections int64 - concurrent_connections int64 - connected_time int64 - busy_time int64 - cpu_time int64 - bytes_received int64 - bytes_sent int64 - binlog_bytes_written int64 - rows_fetched int64 - rows_updated int64 - table_rows_read int64 - select_commands int64 - update_commands int64 - other_commands int64 - commit_transactions int64 - rollback_transactions int64 - denied_connections int64 - lost_connections int64 - access_denied int64 - empty_queries int64 - total_ssl_connections int64 - count uint32 - ) + + cols, err := columnsToLower(rows.Columns()) + if err != nil { + return err + } + + read, err := getColSlice(len(cols)) + if err != nil { + return err + } servtag := getDSNTag(serv) for rows.Next() { - err = rows.Scan(&user, &total_connections, &concurrent_connections, - &connected_time, &busy_time, &cpu_time, &bytes_received, &bytes_sent, &binlog_bytes_written, - &rows_fetched, &rows_updated, &table_rows_read, &select_commands, &update_commands, &other_commands, - &commit_transactions, &rollback_transactions, &denied_connections, &lost_connections, &access_denied, - &empty_queries, &total_ssl_connections, &count, - ) + err = rows.Scan(read...) if err != nil { return err } - tags := map[string]string{"server": servtag, "user": user} - fields := map[string]interface{}{ + tags := map[string]string{"server": servtag, "user": *read[0].(*string)} + fields := map[string]interface{}{} - "total_connections": total_connections, - "concurrent_connections": concurrent_connections, - "connected_time": connected_time, - "busy_time": busy_time, - "cpu_time": cpu_time, - "bytes_received": bytes_received, - "bytes_sent": bytes_sent, - "binlog_bytes_written": binlog_bytes_written, - "rows_fetched": rows_fetched, - "rows_updated": rows_updated, - "table_rows_read": table_rows_read, - "select_commands": select_commands, - "update_commands": update_commands, - "other_commands": other_commands, - "commit_transactions": commit_transactions, - "rollback_transactions": rollback_transactions, - "denied_connections": denied_connections, - "lost_connections": lost_connections, - "access_denied": access_denied, - "empty_queries": empty_queries, - "total_ssl_connections": total_ssl_connections, + for i := range cols { + if i == 0 { + continue // skip "user" + } + switch v := read[i].(type) { + case *int64: + fields[cols[i]] = *v + case *float64: + fields[cols[i]] = *v + case *string: + fields[cols[i]] = *v + default: + return fmt.Errorf("Unknown column type - %T", v) + } } acc.AddFields("mysql_user_stats", fields, tags) } return nil } +// columnsToLower converts selected column names to lowercase. +func columnsToLower(s []string, e error) ([]string, error) { + if e != nil { + return nil, e + } + d := make([]string, len(s)) + + for i := range s { + d[i] = strings.ToLower(s[i]) + } + return d, nil +} + +// getColSlice returns an in interface slice that can be used in the row.Scan(). +func getColSlice(l int) ([]interface{}, error) { + // list of all possible column names + var ( + user string + total_connections int64 + concurrent_connections int64 + connected_time int64 + busy_time int64 + cpu_time int64 + bytes_received int64 + bytes_sent int64 + binlog_bytes_written int64 + rows_read int64 + rows_sent int64 + rows_deleted int64 + rows_inserted int64 + rows_updated int64 + select_commands int64 + update_commands int64 + other_commands int64 + commit_transactions int64 + rollback_transactions int64 + denied_connections int64 + lost_connections int64 + access_denied int64 + empty_queries int64 + total_ssl_connections int64 + max_statement_time_exceeded int64 + // maria specific + fbusy_time float64 + fcpu_time float64 + // percona specific + rows_fetched int64 + table_rows_read int64 + ) + + switch l { + case 23: // maria5 + return []interface{}{ + &user, + &total_connections, + &concurrent_connections, + &connected_time, + &fbusy_time, + &fcpu_time, + &bytes_received, + &bytes_sent, + &binlog_bytes_written, + &rows_read, + &rows_sent, + &rows_deleted, + &rows_inserted, + &rows_updated, + &select_commands, + &update_commands, + &other_commands, + &commit_transactions, + &rollback_transactions, + &denied_connections, + &lost_connections, + &access_denied, + &empty_queries, + }, nil + case 25: // maria10 + return []interface{}{ + &user, + &total_connections, + &concurrent_connections, + &connected_time, + &fbusy_time, + &fcpu_time, + &bytes_received, + &bytes_sent, + &binlog_bytes_written, + &rows_read, + &rows_sent, + &rows_deleted, + &rows_inserted, + &rows_updated, + &select_commands, + &update_commands, + &other_commands, + &commit_transactions, + &rollback_transactions, + &denied_connections, + &lost_connections, + &access_denied, + &empty_queries, + &total_ssl_connections, + &max_statement_time_exceeded, + }, nil + case 22: // percona + return []interface{}{ + &user, + &total_connections, + &concurrent_connections, + &connected_time, + &busy_time, + &cpu_time, + &bytes_received, + &bytes_sent, + &binlog_bytes_written, + &rows_fetched, + &rows_updated, + &table_rows_read, + &select_commands, + &update_commands, + &other_commands, + &commit_transactions, + &rollback_transactions, + &denied_connections, + &lost_connections, + &access_denied, + &empty_queries, + &total_ssl_connections, + }, nil + } + + return nil, fmt.Errorf("Not Supported - %d columns", l) +} + // gatherPerfTableIOWaits can be used to get total count and time // of I/O wait event for each table and process func (m *Mysql) gatherPerfTableIOWaits(db *sql.DB, serv string, acc telegraf.Accumulator) error { @@ -1272,7 +1157,11 @@ func (m *Mysql) gatherInfoSchemaAutoIncStatuses(db *sql.DB, serv string, acc tel fields["auto_increment_column"] = incValue fields["auto_increment_column_max"] = maxInt - acc.AddFields("mysql_info_schema", fields, tags) + if m.MetricVersion < 2 { + acc.AddFields("mysql_info_schema", fields, tags) + } else { + acc.AddFields("mysql_table_schema", fields, tags) + } } return nil } @@ -1287,21 +1176,19 @@ func (m *Mysql) gatherInnoDBMetrics(db *sql.DB, serv string, acc telegraf.Accumu } defer rows.Close() - var key string - var val sql.RawBytes - // parse DSN and save server tag servtag := getDSNTag(serv) tags := map[string]string{"server": servtag} fields := make(map[string]interface{}) for rows.Next() { + var key string + var val sql.RawBytes if err := rows.Scan(&key, &val); err != nil { return err } key = strings.ToLower(key) - // parse value, if it is numeric then save, otherwise ignore - if floatVal, ok := parseValue(val); ok { - fields[key] = floatVal + if value, ok := m.parseValue(val); ok { + fields[key] = value } // Send 20 fields at a time if len(fields) >= 20 { @@ -1671,23 +1558,37 @@ func (m *Mysql) gatherTableSchema(db *sql.DB, serv string, acc telegraf.Accumula tags["schema"] = tableSchema tags["table"] = tableName - acc.AddFields(newNamespace("info_schema", "table_rows"), - map[string]interface{}{"value": tableRows}, tags) + if m.MetricVersion < 2 { + acc.AddFields(newNamespace("info_schema", "table_rows"), + map[string]interface{}{"value": tableRows}, tags) + + dlTags := copyTags(tags) + dlTags["component"] = "data_length" + acc.AddFields(newNamespace("info_schema", "table_size", "data_length"), + map[string]interface{}{"value": dataLength}, dlTags) - dlTags := copyTags(tags) - dlTags["component"] = "data_length" - acc.AddFields(newNamespace("info_schema", "table_size", "data_length"), - map[string]interface{}{"value": dataLength}, dlTags) + ilTags := copyTags(tags) + ilTags["component"] = "index_length" + acc.AddFields(newNamespace("info_schema", "table_size", "index_length"), + map[string]interface{}{"value": indexLength}, ilTags) - ilTags := copyTags(tags) - ilTags["component"] = "index_length" - acc.AddFields(newNamespace("info_schema", "table_size", "index_length"), - map[string]interface{}{"value": indexLength}, ilTags) + dfTags := copyTags(tags) + dfTags["component"] = "data_free" + acc.AddFields(newNamespace("info_schema", "table_size", "data_free"), + map[string]interface{}{"value": dataFree}, dfTags) + } else { + acc.AddFields("mysql_table_schema", + map[string]interface{}{"rows": tableRows}, tags) - dfTags := copyTags(tags) - dfTags["component"] = "data_free" - acc.AddFields(newNamespace("info_schema", "table_size", "data_free"), - map[string]interface{}{"value": dataFree}, dfTags) + acc.AddFields("mysql_table_schema", + map[string]interface{}{"data_length": dataLength}, tags) + + acc.AddFields("mysql_table_schema", + map[string]interface{}{"index_length": indexLength}, tags) + + acc.AddFields("mysql_table_schema", + map[string]interface{}{"data_free": dataFree}, tags) + } versionTags := copyTags(tags) versionTags["type"] = tableType @@ -1695,24 +1596,47 @@ func (m *Mysql) gatherTableSchema(db *sql.DB, serv string, acc telegraf.Accumula versionTags["row_format"] = rowFormat versionTags["create_options"] = createOptions - acc.AddFields(newNamespace("info_schema", "table_version"), - map[string]interface{}{"value": version}, versionTags) + if m.MetricVersion < 2 { + acc.AddFields(newNamespace("info_schema", "table_version"), + map[string]interface{}{"value": version}, versionTags) + } else { + acc.AddFields("mysql_table_schema_version", + map[string]interface{}{"table_version": version}, versionTags) + } } } return nil } +func (m *Mysql) parseValue(value sql.RawBytes) (interface{}, bool) { + if m.MetricVersion < 2 { + return v1.ParseValue(value) + } else { + return parseValue(value) + } +} + // parseValue can be used to convert values such as "ON","OFF","Yes","No" to 0,1 -func parseValue(value sql.RawBytes) (float64, bool) { - if bytes.Compare(value, []byte("Yes")) == 0 || bytes.Compare(value, []byte("ON")) == 0 { +func parseValue(value sql.RawBytes) (interface{}, bool) { + if bytes.EqualFold(value, []byte("YES")) || bytes.Compare(value, []byte("ON")) == 0 { return 1, true } - if bytes.Compare(value, []byte("No")) == 0 || bytes.Compare(value, []byte("OFF")) == 0 { + if bytes.EqualFold(value, []byte("NO")) || bytes.Compare(value, []byte("OFF")) == 0 { return 0, true } - n, err := strconv.ParseFloat(string(value), 64) - return n, err == nil + + if val, err := strconv.ParseInt(string(value), 10, 64); err == nil { + return val, true + } + if val, err := strconv.ParseFloat(string(value), 64); err == nil { + return val, true + } + + if len(string(value)) > 0 { + return string(value), true + } + return nil, false } // findThreadState can be used to find thread state by command and plain state diff --git a/plugins/inputs/mysql/mysql_test.go b/plugins/inputs/mysql/mysql_test.go index 5356e7bd4eaba..b4983ba0e028f 100644 --- a/plugins/inputs/mysql/mysql_test.go +++ b/plugins/inputs/mysql/mysql_test.go @@ -49,7 +49,7 @@ func TestMysqlGetDSNTag(t *testing.T) { }, { "tcp(localhost)/", - "localhost", + "localhost:3306", }, { "root:passwd@tcp(192.168.1.1:3306)/?tls=false", @@ -127,26 +127,29 @@ func TestMysqlDNSAddTimeout(t *testing.T) { } } } - func TestParseValue(t *testing.T) { testCases := []struct { rawByte sql.RawBytes - value float64 + output interface{} boolValue bool }{ - {sql.RawBytes("Yes"), 1, true}, - {sql.RawBytes("No"), 0, false}, + {sql.RawBytes("123"), int64(123), true}, + {sql.RawBytes("abc"), "abc", true}, + {sql.RawBytes("10.1"), 10.1, true}, {sql.RawBytes("ON"), 1, true}, - {sql.RawBytes("OFF"), 0, false}, - {sql.RawBytes("ABC"), 0, false}, + {sql.RawBytes("OFF"), 0, true}, + {sql.RawBytes("NO"), 0, true}, + {sql.RawBytes("YES"), 1, true}, + {sql.RawBytes("No"), 0, true}, + {sql.RawBytes("Yes"), 1, true}, + {sql.RawBytes(""), nil, false}, } for _, cases := range testCases { - if value, ok := parseValue(cases.rawByte); value != cases.value && ok != cases.boolValue { - t.Errorf("want %d with %t, got %d with %t", int(cases.value), cases.boolValue, int(value), ok) + if got, ok := parseValue(cases.rawByte); got != cases.output && ok != cases.boolValue { + t.Errorf("for %s wanted %t, got %t", string(cases.rawByte), cases.output, got) } } } - func TestNewNamespace(t *testing.T) { testCases := []struct { words []string diff --git a/plugins/inputs/mysql/v1/mysql.go b/plugins/inputs/mysql/v1/mysql.go new file mode 100644 index 0000000000000..6f6062d14f4db --- /dev/null +++ b/plugins/inputs/mysql/v1/mysql.go @@ -0,0 +1,195 @@ +package v1 + +import ( + "bytes" + "database/sql" + "strconv" +) + +type Mapping struct { + OnServer string + InExport string +} + +var Mappings = []*Mapping{ + { + OnServer: "Aborted_", + InExport: "aborted_", + }, + { + OnServer: "Bytes_", + InExport: "bytes_", + }, + { + OnServer: "Com_", + InExport: "commands_", + }, + { + OnServer: "Created_", + InExport: "created_", + }, + { + OnServer: "Handler_", + InExport: "handler_", + }, + { + OnServer: "Innodb_", + InExport: "innodb_", + }, + { + OnServer: "Key_", + InExport: "key_", + }, + { + OnServer: "Open_", + InExport: "open_", + }, + { + OnServer: "Opened_", + InExport: "opened_", + }, + { + OnServer: "Qcache_", + InExport: "qcache_", + }, + { + OnServer: "Table_", + InExport: "table_", + }, + { + OnServer: "Tokudb_", + InExport: "tokudb_", + }, + { + OnServer: "Threads_", + InExport: "threads_", + }, + { + OnServer: "Access_", + InExport: "access_", + }, + { + OnServer: "Aria__", + InExport: "aria_", + }, + { + OnServer: "Binlog__", + InExport: "binlog_", + }, + { + OnServer: "Busy_", + InExport: "busy_", + }, + { + OnServer: "Connection_", + InExport: "connection_", + }, + { + OnServer: "Delayed_", + InExport: "delayed_", + }, + { + OnServer: "Empty_", + InExport: "empty_", + }, + { + OnServer: "Executed_", + InExport: "executed_", + }, + { + OnServer: "Executed_", + InExport: "executed_", + }, + { + OnServer: "Feature_", + InExport: "feature_", + }, + { + OnServer: "Flush_", + InExport: "flush_", + }, + { + OnServer: "Last_", + InExport: "last_", + }, + { + OnServer: "Master_", + InExport: "master_", + }, + { + OnServer: "Max_", + InExport: "max_", + }, + { + OnServer: "Memory_", + InExport: "memory_", + }, + { + OnServer: "Not_", + InExport: "not_", + }, + { + OnServer: "Performance_", + InExport: "performance_", + }, + { + OnServer: "Prepared_", + InExport: "prepared_", + }, + { + OnServer: "Rows_", + InExport: "rows_", + }, + { + OnServer: "Rpl_", + InExport: "rpl_", + }, + { + OnServer: "Select_", + InExport: "select_", + }, + { + OnServer: "Slave_", + InExport: "slave_", + }, + { + OnServer: "Slow_", + InExport: "slow_", + }, + { + OnServer: "Sort_", + InExport: "sort_", + }, + { + OnServer: "Subquery_", + InExport: "subquery_", + }, + { + OnServer: "Tc_", + InExport: "tc_", + }, + { + OnServer: "Threadpool_", + InExport: "threadpool_", + }, + { + OnServer: "wsrep_", + InExport: "wsrep_", + }, + { + OnServer: "Uptime_", + InExport: "uptime_", + }, +} + +func ParseValue(value sql.RawBytes) (float64, bool) { + if bytes.Compare(value, []byte("Yes")) == 0 || bytes.Compare(value, []byte("ON")) == 0 { + return 1, true + } + + if bytes.Compare(value, []byte("No")) == 0 || bytes.Compare(value, []byte("OFF")) == 0 { + return 0, true + } + n, err := strconv.ParseFloat(string(value), 64) + return n, err == nil +} diff --git a/testutil/pki/cacert.pem b/testutil/pki/cacert.pem new file mode 100644 index 0000000000000..b0a47334e83fe --- /dev/null +++ b/testutil/pki/cacert.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIB0TCCATqgAwIBAgIJAMgbq6rkA4b/MA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV +BAMMEFRlbGVncmFmIFRlc3QgQ0EwHhcNMTgwNTAzMDEwNTI5WhcNMjgwNDMwMDEw +NTI5WjAbMRkwFwYDVQQDDBBUZWxlZ3JhZiBUZXN0IENBMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDTySxyXeyQQjCOtNQ/7cKtXN91sp4B1k7whPKBO6yXEFFR +rYaw76xY5CTTPTJaAPBJ+amHPdPGfmGq6yX10tjAaWQQYV26Axngfpti6F14ci0/ +X/sTay8ii/4Du5DRr9f9rHVimPASR1fkgK+IFhXnONn1R+pNbHYmGS4OVNyoPwID +AQABox0wGzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsF +AAOBgQA9v3eMU33q+bGPEd65kKQcVddPEFdSqmuUJMeO2VQmUFc/ejkP48u42eDK +Y1GAR+209XgkuWItEBH8HJysOU2plunuIPXpnPcxyP30tpFVLaWzWTQvUehhYpfQ +C0v9Re3jdLfLORxiaAPyyKogMpAQrjGX+u1aMSOCkcTD2Hjvbw== +-----END CERTIFICATE----- diff --git a/testutil/pki/cakey.pem b/testutil/pki/cakey.pem new file mode 100644 index 0000000000000..3606c89beface --- /dev/null +++ b/testutil/pki/cakey.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANPJLHJd7JBCMI60 +1D/twq1c33WyngHWTvCE8oE7rJcQUVGthrDvrFjkJNM9MloA8En5qYc908Z+Yarr +JfXS2MBpZBBhXboDGeB+m2LoXXhyLT9f+xNrLyKL/gO7kNGv1/2sdWKY8BJHV+SA +r4gWFec42fVH6k1sdiYZLg5U3Kg/AgMBAAECgYA2PCtssk7Vdo3WzcoZAPs8yC7V +hkNedxJKF9G+dJizKtOYVhbLEuWQ8gPYMLDHSbw/RXc7kgK8rzq1uXhEJpWo4THD +CUUlxGRu3gt94202hbnEnV93Kix4hP98qpv1jPErlx2KywsRPTegMnUAZ2xeI564 +yYwDITqXALa/PqRqSQJBAPPZQeRDtBSfEjZFJS3IgUkmN3RJn4rJz+6D0ahgXPga +YAYVe8SJyj2epLJP2aOBzrqBSUVkVGg8qOG5w+ibebsCQQDeVuUzYOffthO5f1Hl +LvdEmfaHjXI0Q+grOnDjNRcvQaCDYYkC9JewBQmnpFrd85rN/Leo0gQ5Yyxp/ja5 +gPFNAkAFwn/38FF0mz1G4uM57Z6AJ9LvgD2wfYvXym1NWNlZUuYpvqApyEdqpTCm +tZQidJJ5fUxJw1DrFWO30Td7axC5AkEAjSbRX6rXyhiHsS35SexlInI0Jp5PsIqj +7D2vyS69R0z8oCvdlbi+TAsGtB0Navbqgnc8Cbs630vsuGWhTGdlyQJBAKqQ2gYw ++WeXH77FP8yDQOjpFw80tSyXVykT0Am75RF3sQ1OIn0o0DLhE+he0crb2n8g3FJh +WyxmGkbTDelSG20= +-----END PRIVATE KEY----- diff --git a/testutil/pki/clientcert.pem b/testutil/pki/clientcert.pem new file mode 100644 index 0000000000000..9e5b608078231 --- /dev/null +++ b/testutil/pki/clientcert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB+TCCAWKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBUZWxl +Z3JhZiBUZXN0IENBMB4XDTE4MDUwMzAxMDUyOVoXDTI4MDQzMDAxMDUyOVowHTEb +MBkGA1UEAwwSY2xpZW50LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDX7Plvu0MJtA9TrusYtQnAogsdiYJZd9wfFIjH5FxE3SWJ4KAIE+yR +WRqcqX8XnpieQLaNsfXhDPWLkWngTDydk4NO/jlAQk0e6+9+NeiZ2ViIHmtXERb9 +CyiiWUmo+YCd69lhzSEIMK9EPBSDHQTgQMtEfGak03G5rx3MCakE1QIDAQABo0sw +STAJBgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAaBgNVHREEEzARgglsb2NhbGhvc3SH +BH8AAAEwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEAVry0 +L07oTN+FMLncY/Be9BzFB3b3mnbxbZr58OgI4WHuOeYBuvDI033FIIIzpwb8XYpG +HJkZlSbviqq19lAh/Cktl35BCNrA6Uc+dgW7QWhnYS2tZandVTo/8FFstJTNiiLw +uiz/Hr3mRXUIDi5OygJHY1IZr8hFTOOJY+0ws3E= +-----END CERTIFICATE----- diff --git a/testutil/pki/clientkey.pem b/testutil/pki/clientkey.pem new file mode 100644 index 0000000000000..cc11e20eaca1e --- /dev/null +++ b/testutil/pki/clientkey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDX7Plvu0MJtA9TrusYtQnAogsdiYJZd9wfFIjH5FxE3SWJ4KAI +E+yRWRqcqX8XnpieQLaNsfXhDPWLkWngTDydk4NO/jlAQk0e6+9+NeiZ2ViIHmtX +ERb9CyiiWUmo+YCd69lhzSEIMK9EPBSDHQTgQMtEfGak03G5rx3MCakE1QIDAQAB +AoGAOjRU4Lt3zKvO3d3u3ZAfet+zY1jn3DolCfO9EzUJcj6ymcIFIWhNgrikJcrC +yZkkxrPnAbcQ8oNNxTuDcMTcKZbnyUnlQj5NtVuty5Q+zgf3/Q2pRhaE+TwrpOJ+ +ETtVp9R/PrPN2NC5wPo289fPNWFYkd4DPbdWZp5AJHz1XYECQQD3kKpinJxMYp9F +Q1Qj1OkxGln0KPgdqRYjjW/rXI4/hUodfg+xXWHPFSGj3AgEjQIvuengbOAeH3qo +wF1uxVTlAkEA30hXM3EbboMCDQzNRNkkV9EiZ0MZXhj1aIGl+sQZOmOeFdcdjGkD +dsA42nmaYqXCD9KAvc+S/tGJaa0Qg0VhMQJAb2+TAqh0Qn3yK39PFIH2JcAy1ZDL +fq5p5L75rfwPm9AnuHbSIYhjSo+8gMG+ai3+2fTZrcfUajrJP8S3SfFRcQJBANQQ +POHatxcKzlPeqMaPBXlyY553mAxK4CnVmPLGdL+EBYzwtlu5EVUj09uMSxkOHXYx +k5yzHQVvtXbsrBZBOsECQBJLlkMjJmXrIIdLPmHQWL3bm9MMg1PqzupSEwz6cyrG +uIIm/X91pDyxCHaKYWp38FXBkYAgohI8ow5/sgRvU5w= +-----END RSA PRIVATE KEY----- diff --git a/testutil/pki/servercert.pem b/testutil/pki/servercert.pem new file mode 100644 index 0000000000000..8862195179889 --- /dev/null +++ b/testutil/pki/servercert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB+TCCAWKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBUZWxl +Z3JhZiBUZXN0IENBMB4XDTE4MDUwMzAxMDUyOVoXDTI4MDQzMDAxMDUyOVowHTEb +MBkGA1UEAwwSc2VydmVyLmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDTBmLJ0pBFUxnPkkx38sBnOKvs+OinVqxTnVcc1iCyQJQleB37uY6D +L55mSsPvnad/oDpyGpHt4RVtrhmyC6ptSrWLyk7mraeAo30Cooqr5tA9A+6yj0ij +ySLlYimTMQy8tbnVNWLwKbxgT9N4NlUzwyqxLWUMfRzLfmefqzk5bQIDAQABo0sw +STAJBgNVHRMEAjAAMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATALBgNVHQ8E +BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADgYEATNnM +ol0s29lJ+WkP+HUFtKaXxQ+kXLADqfhsk2G1/kZAVRHsYUDlJ+GkHnWIHlg/ggIP +JS+z44iwMPOtzJQI7MvAFYVKpYAEdIFTjXf6GafLjUfoXYi0vwHoVJHtQu3Kpm9L +Ugm02h0ycIadN8RdWAAFUf6XpVKUJa0YYLuyaXY= +-----END CERTIFICATE----- diff --git a/testutil/pki/serverkey.pem b/testutil/pki/serverkey.pem new file mode 100644 index 0000000000000..363f5d9af5725 --- /dev/null +++ b/testutil/pki/serverkey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDTBmLJ0pBFUxnPkkx38sBnOKvs+OinVqxTnVcc1iCyQJQleB37 +uY6DL55mSsPvnad/oDpyGpHt4RVtrhmyC6ptSrWLyk7mraeAo30Cooqr5tA9A+6y +j0ijySLlYimTMQy8tbnVNWLwKbxgT9N4NlUzwyqxLWUMfRzLfmefqzk5bQIDAQAB +AoGBALWQAgFJxM2QwV1hr59oYnitPudmBa6smRpb/q6V4Y3cmFpgrdN+hIqEtxGl +9E0+5PWfI4o3KCV2itxSdlNFTDyqTZkM+BT8PPKISzAewkdqnKjbWgAmluzOJH4O +hc1zBfIOuT5+cfx5JR5/j9BhWVC7BJ+EiREkd/Z8ZnAMeItVAkEA8bhcC+8luiFQ +6kytXx2XfbKKh4Q99+KEQHqSGeuHZOcnWfjX99jo67CIxpwBRENslpZOw78fBmi4 +4kf8j+dgLwJBAN99zyRxYzKc8TSsy/fF+3V/Ex75HYGGS/eOWcwPFXpGNA63hIa8 +fJ/2pDnLzCqLZ9vWdBF39NtkacJS7bo6XSMCQQCZgN2bipSn3k53bJhRJga1gXOt +2dJMoGIiXHR513QVJSJ9ZaUpNWu9eU9y6VF4m2TTQMLmVnIKbOi0csi2TlZrAkAi +7URsC5RXGpPPiZmutTAhIqTYWFI2JcjFfWenLkxK+aG1ExURAW/wh9kOdz0HARZQ +Eum8uSR5DO5CQjeIvQpFAkAgZJXAwRxuts/p1EoLuPCJTaDkIY2vc0AJzzr5nuAs +pyjnLYCYqSBUJ+3nDDBqNYpgxCJddzmjNxGuO7mef9Ue +-----END RSA PRIVATE KEY----- diff --git a/testutil/pki/tls-certs.sh b/testutil/pki/tls-certs.sh new file mode 100644 index 0000000000000..55075df4bd1b7 --- /dev/null +++ b/testutil/pki/tls-certs.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +mkdir certs certs_by_serial private && +chmod 700 private && +echo 01 > ./serial && +touch ./index.txt && +cat >./openssl.conf <