From 79d4e9e9a96c6c873d2ebdef708579aab1079656 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Thu, 17 Jun 2021 21:10:22 +0530 Subject: [PATCH] DB RPC driver: add master/replica support (#17792) Automatic Merge --- app/plugin_api_tests/test_db_driver/main.go | 21 +++++++++++++-------- app/plugin_db_driver.go | 8 ++++++-- plugin/client.go | 1 + plugin/db_rpc.go | 8 ++++---- plugin/helpers.go | 6 +++++- shared/driver/driver.go | 12 ++++++++---- 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/app/plugin_api_tests/test_db_driver/main.go b/app/plugin_api_tests/test_db_driver/main.go index f7b5e771b1b..96311dd619c 100644 --- a/app/plugin_api_tests/test_db_driver/main.go +++ b/app/plugin_api_tests/test_db_driver/main.go @@ -32,15 +32,20 @@ func (p *MyPlugin) MessageWillBePosted(_ *plugin.Context, _ *model.Post) (*model store := sqlstore.New(p.API.GetUnsanitizedConfig().SqlSettings, nil) store.GetMaster().Db.Close() - store.GetMaster().Db = sql.OpenDB(driver.NewConnector(p.Driver)) - defer store.GetMaster().Db.Close() + for _, isMaster := range []bool{true, false} { + // We replace the master DB with master and replica both just to make + // gorp APIs work. + store.GetMaster().Db = sql.OpenDB(driver.NewConnector(p.Driver, isMaster)) - // Testing with a handful of stores - storetest.TestPostStore(p.t, store, store) - storetest.TestUserStore(p.t, store, store) - storetest.TestTeamStore(p.t, store) - storetest.TestChannelStore(p.t, store, store) - storetest.TestBotStore(p.t, store, store) + // Testing with a handful of stores + storetest.TestPostStore(p.t, store, store) + storetest.TestUserStore(p.t, store, store) + storetest.TestTeamStore(p.t, store) + storetest.TestChannelStore(p.t, store, store) + storetest.TestBotStore(p.t, store, store) + + store.GetMaster().Db.Close() + } // Use the API to instantiate the driver // And then run the full suite of tests. diff --git a/app/plugin_db_driver.go b/app/plugin_db_driver.go index 65e352e926c..11a64b2d1bf 100644 --- a/app/plugin_db_driver.go +++ b/app/plugin_db_driver.go @@ -39,8 +39,12 @@ func NewDriverImpl(s *Server) *DriverImpl { } } -func (d *DriverImpl) Conn() (string, error) { - conn, err := d.s.sqlStore.GetMaster().Db.Conn(context.Background()) +func (d *DriverImpl) Conn(isMaster bool) (string, error) { + dbFunc := d.s.sqlStore.GetMaster + if !isMaster { + dbFunc = d.s.sqlStore.GetReplica + } + conn, err := dbFunc().Db.Conn(context.Background()) if err != nil { return "", err } diff --git a/plugin/client.go b/plugin/client.go index 15ac0254c9d..d5a8789943c 100644 --- a/plugin/client.go +++ b/plugin/client.go @@ -56,6 +56,7 @@ func (p *MattermostPlugin) SetHelpers(helpers Helpers) { p.Helpers = helpers } +// SetDriver sets the RPC client implementation to talk with the server. func (p *MattermostPlugin) SetDriver(driver Driver) { p.Driver = driver } diff --git a/plugin/db_rpc.go b/plugin/db_rpc.go index 0d2701f24da..06a556387fe 100644 --- a/plugin/db_rpc.go +++ b/plugin/db_rpc.go @@ -42,9 +42,9 @@ type Z_DbBoolReturn struct { A bool } -func (db *dbRPCClient) Conn() (string, error) { +func (db *dbRPCClient) Conn(isMaster bool) (string, error) { ret := &Z_DbStrErrReturn{} - err := db.client.Call("Plugin.Conn", struct{}{}, ret) + err := db.client.Call("Plugin.Conn", isMaster, ret) if err != nil { log.Printf("error during Plugin.Conn: %v", err) } @@ -52,8 +52,8 @@ func (db *dbRPCClient) Conn() (string, error) { return ret.A, ret.B } -func (db *dbRPCServer) Conn(_ struct{}, ret *Z_DbStrErrReturn) error { - ret.A, ret.B = db.dbImpl.Conn() +func (db *dbRPCServer) Conn(isMaster bool, ret *Z_DbStrErrReturn) error { + ret.A, ret.B = db.dbImpl.Conn(isMaster) ret.B = encodableError(ret.B) return nil } diff --git a/plugin/helpers.go b/plugin/helpers.go index 68885dd78fc..3c722dc6fe4 100644 --- a/plugin/helpers.go +++ b/plugin/helpers.go @@ -109,9 +109,13 @@ type ResultContainer struct { RowsAffectedError error } +// Driver is a sql driver interface that is used by plugins to perform +// raw SQL queries without opening DB connections by themselves. This interface +// is not subject to backward compatibility guarantees and is only meant to be +// used by plugins built by the Mattermost team. type Driver interface { // Connection - Conn() (string, error) + Conn(isMaster bool) (string, error) ConnPing(connID string) error ConnClose(connID string) error ConnQuery(connID, q string, args []driver.NamedValue) (string, error) // rows diff --git a/shared/driver/driver.go b/shared/driver/driver.go index a917c874041..3093db8e39e 100644 --- a/shared/driver/driver.go +++ b/shared/driver/driver.go @@ -23,15 +23,19 @@ var ( // Connector is the DB connector which is used to // communicate with the DB API. type Connector struct { - api plugin.Driver + api plugin.Driver + isMaster bool } -func NewConnector(api plugin.Driver) *Connector { - return &Connector{api: api} +// NewConnector returns a DB connector that can be used to return a sql.DB object. +// It takes a plugin.Driver implementation and a boolean flag to indicate whether +// to connect to a master or replica DB instance. +func NewConnector(api plugin.Driver, isMaster bool) *Connector { + return &Connector{api: api, isMaster: isMaster} } func (c *Connector) Connect(_ context.Context) (driver.Conn, error) { - connID, err := c.api.Conn() + connID, err := c.api.Conn(c.isMaster) if err != nil { return nil, err }