From c0ca2fab6c1a51eb59baf92037d4fe94965c357e Mon Sep 17 00:00:00 2001 From: Gabriel Nelle Date: Sat, 27 May 2023 10:58:14 +0200 Subject: [PATCH 1/8] add method to query to be able to retrieve the Values --- session.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/session.go b/session.go index 3b0d71c8d..8bcfafadc 100644 --- a/session.go +++ b/session.go @@ -935,6 +935,12 @@ func (q Query) Statement() string { return q.stmt } +// Values returns the values passed in via Bind. This can be helpful for wrapping types +// to not have to keep track of the values to be able to access them. +func (q Query) Values() []interface{} { + return q.values +} + // String implements the stringer interface. func (q Query) String() string { return fmt.Sprintf("[query statement=%q values=%+v consistency=%s]", q.stmt, q.values, q.cons) From 73398bd50d44354376363f80459b160107efe624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Przytu=C5=82a?= Date: Fri, 26 May 2023 13:56:24 +0200 Subject: [PATCH 2/8] conn: Advertise driver's name & version in STARTUP Advertising driver's name in the system.clients table can be helpful when debugging issues, e.g. when a connection imbalance occurs and allows to narrow down the culprit application/driver better. --- conn.go | 4 +++- version.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 version.go diff --git a/conn.go b/conn.go index 9fb40c488..7b793b4cd 100644 --- a/conn.go +++ b/conn.go @@ -422,7 +422,9 @@ func (s *startupCoordinator) options(ctx context.Context) error { func (s *startupCoordinator) startup(ctx context.Context, supported map[string][]string) error { m := map[string]string{ - "CQL_VERSION": s.conn.cfg.CQLVersion, + "CQL_VERSION": s.conn.cfg.CQLVersion, + "DRIVER_NAME": driverName, + "DRIVER_VERSION": driverVersion, } if s.conn.compressor != nil { diff --git a/version.go b/version.go new file mode 100644 index 000000000..015b40e1e --- /dev/null +++ b/version.go @@ -0,0 +1,28 @@ +package gocql + +import "runtime/debug" + +const ( + mainModule = "github.com/gocql/gocql" +) + +var driverName string + +var driverVersion string + +func init() { + buildInfo, ok := debug.ReadBuildInfo() + if ok { + for _, d := range buildInfo.Deps { + if d.Path == mainModule { + driverName = mainModule + driverVersion = d.Version + if d.Replace != nil { + driverName = d.Replace.Path + driverVersion = d.Replace.Version + } + break + } + } + } +} From 640e5ffe9e3a91d6a035f837d045e3ab5e5b0f02 Mon Sep 17 00:00:00 2001 From: Martin Sucha Date: Mon, 12 Jun 2023 13:10:26 +0200 Subject: [PATCH 3/8] Update changelog for 1.5.0 --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17b2fb756..2ff9743d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +### Fixed + +## [1.5.0] - 2023-06-12 + +### Added + +- gocql now advertises the driver name and version in the STARTUP message to the server. + The values are taken from the Go module's path and version + (or from the replacement module, if used). (#1702) + That allows the server to track which fork of the driver is being used. +- Query.Values() to retrieve the values bound to the Query. + This makes writing wrappers around Query easier. (#1700) + ### Fixed - Potential panic on deserialization (#1695) - Unmarshalling of dates outside of `[1677-09-22, 2262-04-11]` range. (#1692) From 86b5752ed989e8bd1f0e551987a21480000ddabe Mon Sep 17 00:00:00 2001 From: Martin Sucha Date: Mon, 12 Jun 2023 13:19:43 +0200 Subject: [PATCH 4/8] Update documentation of Query.Values --- session.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/session.go b/session.go index 8bcfafadc..0263d564d 100644 --- a/session.go +++ b/session.go @@ -935,8 +935,8 @@ func (q Query) Statement() string { return q.stmt } -// Values returns the values passed in via Bind. This can be helpful for wrapping types -// to not have to keep track of the values to be able to access them. +// Values returns the values passed in via Bind. +// This can be used by a wrapper type that needs to access the bound values. func (q Query) Values() []interface{} { return q.values } From c27e8098fa3e9e2d59e114a63e8d09f3729fc472 Mon Sep 17 00:00:00 2001 From: Martin Sucha Date: Mon, 12 Jun 2023 13:29:01 +0200 Subject: [PATCH 5/8] Mention version advertising in documentation --- doc.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc.go b/doc.go index b48cef88e..6739d98e4 100644 --- a/doc.go +++ b/doc.go @@ -30,6 +30,9 @@ // protocol version explicitly, as it's not defined which version will be used in certain situations (for example // during upgrade of the cluster when some of the nodes support different set of protocol versions than other nodes). // +// The driver advertises the module name and version in the STARTUP message, so servers are able to detect the version. +// If you use replace directive in go.mod, the driver will send information about the replacement module instead. +// // When ready, create a session from the configuration. Don't forget to Close the session once you are done with it: // // session, err := cluster.CreateSession() From 46ebac819ea1ba5efb9e1078b293114815af9e26 Mon Sep 17 00:00:00 2001 From: Martin Sucha Date: Mon, 12 Jun 2023 17:43:10 +0200 Subject: [PATCH 6/8] Update changelog for 1.5.1 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ff9743d7..bbb915f8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +## [1.5.1] - 2023-06-12 + +Same as 1.5.0. GitHub does not like gpg signed text in the tag message, +so pushing a new tag. + ## [1.5.0] - 2023-06-12 ### Added From d4a7befd0a6fc713afedc2256fb4811272313551 Mon Sep 17 00:00:00 2001 From: Martin Sucha Date: Mon, 12 Jun 2023 18:10:17 +0200 Subject: [PATCH 7/8] Update changelog for 1.5.2 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbb915f8d..0cd62029e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +## [1.5.2] - 2023-06-12 + +Same as 1.5.0. GitHub does not like gpg signed text in the tag message (even with prefixed armor), +so pushing a new tag. + ## [1.5.1] - 2023-06-12 Same as 1.5.0. GitHub does not like gpg signed text in the tag message, From b9737ddcadbbe8092b27df4f6ab2e6e9f3cf4c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Przytu=C5=82a?= Date: Thu, 22 Jun 2023 15:59:19 +0200 Subject: [PATCH 8/8] Reresolve DNS as fallback when all hosts are unreachable If all nodes in the cluster change their IPs at one time, driver used to no longer be able to ever contact the cluster; the only solution was to restart the driver. A fallback is added to the control connection `reconnect()` logic so that when no known host is reachable, all hostnames provided in ClusterConfig (initial contact points) are reresolved and control connection is attempted to be opened to any of them. If this succeeds, a metadata fetch is issued normally and the whole cluster is discovered with its new IPs. For the cluster to correctly learn new IPs in case that nodes are accessible indirectly (e.g. through a proxy), that is, by translated address and not `rpc_address` or `broadcast_address`, the code introduced in #1682 was extended to remove and re-add a host also when its translated address changed (even when its internal address stays the same). As a bonus, a misnamed variable `hostport` is renamed to a suitable `hostaddr`. --- control.go | 45 +++++++++++++++++++++++++++++++++++---------- host_source.go | 2 +- session.go | 4 ++-- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/control.go b/control.go index bda84ebce..47ec7abaf 100644 --- a/control.go +++ b/control.go @@ -282,7 +282,7 @@ func (c *controlConn) setupConn(conn *Conn) error { } if err := c.registerEvents(conn); err != nil { - return err + return fmt.Errorf("register events: %v", err) } ch := &connHost{ @@ -347,6 +347,20 @@ func (c *controlConn) reconnect() { } defer atomic.StoreInt32(&c.reconnecting, 0) + conn, err := c.attemptReconnect() + + if conn == nil { + c.session.logger.Printf("gocql: unable to reconnect control connection: %v\n", err) + return + } + + err = c.session.refreshRing() + if err != nil { + c.session.logger.Printf("gocql: unable to refresh ring: %v\n", err) + } +} + +func (c *controlConn) attemptReconnect() (*Conn, error) { hosts := c.session.ring.allHosts() hosts = shuffleHosts(hosts) @@ -363,6 +377,25 @@ func (c *controlConn) reconnect() { ch.conn.Close() } + conn, err := c.attemptReconnectToAnyOfHosts(hosts) + + if conn != nil { + return conn, err + } + + c.session.logger.Printf("gocql: unable to connect to any ring node: %v\n", err) + c.session.logger.Printf("gocql: control falling back to initial contact points.\n") + // Fallback to initial contact points, as it may be the case that all known initialHosts + // changed their IPs while keeping the same hostname(s). + initialHosts, resolvErr := addrsToHosts(c.session.cfg.Hosts, c.session.cfg.Port, c.session.logger) + if resolvErr != nil { + return nil, fmt.Errorf("resolve contact points' hostnames: %v", resolvErr) + } + + return c.attemptReconnectToAnyOfHosts(initialHosts) +} + +func (c *controlConn) attemptReconnectToAnyOfHosts(hosts []*HostInfo) (*Conn, error) { var conn *Conn var err error for _, host := range hosts { @@ -379,15 +412,7 @@ func (c *controlConn) reconnect() { conn.Close() conn = nil } - if conn == nil { - c.session.logger.Printf("gocql: control unable to register events: %v\n", err) - return - } - - err = c.session.refreshRing() - if err != nil { - c.session.logger.Printf("gocql: unable to refresh ring: %v\n", err) - } + return conn, err } func (c *controlConn) HandleError(conn *Conn, err error, closed bool) { diff --git a/host_source.go b/host_source.go index ac30bfc55..a0b7058d7 100644 --- a/host_source.go +++ b/host_source.go @@ -714,7 +714,7 @@ func refreshRing(r *ringDescriber) error { if !ok { return fmt.Errorf("get existing host=%s from prevHosts: %w", h, ErrCannotFindHost) } - if h.nodeToNodeAddress().Equal(existing.nodeToNodeAddress()) { + if h.connectAddress.Equal(existing.connectAddress) && h.nodeToNodeAddress().Equal(existing.nodeToNodeAddress()) { // no host IP change host.update(h) } else { diff --git a/session.go b/session.go index 0263d564d..d5ff9ecae 100644 --- a/session.go +++ b/session.go @@ -93,8 +93,8 @@ var queryPool = &sync.Pool{ func addrsToHosts(addrs []string, defaultPort int, logger StdLogger) ([]*HostInfo, error) { var hosts []*HostInfo - for _, hostport := range addrs { - resolvedHosts, err := hostInfo(hostport, defaultPort) + for _, hostaddr := range addrs { + resolvedHosts, err := hostInfo(hostaddr, defaultPort) if err != nil { // Try other hosts if unable to resolve DNS name if _, ok := err.(*net.DNSError); ok {