From f3d9e7b175b4e41216c307fbbe35603dedc06833 Mon Sep 17 00:00:00 2001 From: Vladimir Fidunin Date: Wed, 15 Jan 2025 11:56:54 +0300 Subject: [PATCH] box: add replication information --- CHANGELOG.md | 2 + box/info.go | 53 +++++++++++++++++ box/info_test.go | 126 ++++++++++++++++++++++++++++++++++++++++ box/tarantool_test.go | 6 ++ box/testdata/config.lua | 3 + 5 files changed, 190 insertions(+) create mode 100644 box/info_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index c515f54c2..82f9766f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. ### Added +- Extend box with replication information (#427). + ### Changed ### Fixed diff --git a/box/info.go b/box/info.go index 6e5ed1c92..aabfd65e3 100644 --- a/box/info.go +++ b/box/info.go @@ -26,6 +26,59 @@ type Info struct { Status string `msgpack:"status"` // LSN - Log sequence number of the instance. LSN uint64 `msgpack:"lsn"` + // Replication - replication status. + Replication map[int]Replication `msgpack:"replication,omitempty"` +} + +// Replication section of box.info() is a table with statistics for all instances +// in the replica set that the current instance belongs to. +type Replication struct { + // ID is a short numeric identifier of instance n within the replica set. + ID int `msgpack:"id"` + // UUID - Unique identifier of the instance. + UUID string `msgpack:"uuid"` + // LSN - Log sequence number of the instance. + LSN uint64 `msgpack:"lsn"` + // Upstream - information about upstream. + Upstream Upstream `msgpack:"upstream,omitempty"` + // Downstream - information about downstream. + Downstream Downstream `msgpack:"downstream,omitempty"` +} + +// Upstream information. +type Upstream struct { + // Status is replication status of the connection with the instance. + Status string `msgpack:"status"` + // Idle is the time (in seconds) since the last event was received. + Idle float64 `msgpack:"idle"` + // Peer contains instance n’s URI. + Peer string `msgpack:"peer"` + // Lag is the time difference between the local time of instance n, + // recorded when the event was received, and the local time at another master + // recorded when the event was written to the write-ahead log on that master. + Lag float64 `msgpack:"lag"` + // Message contains an error message in case of a degraded state; otherwise, it is nil. + Message string `msgpack:"message,omitempty"` + // SystemMessage contains an error message in case of a degraded state; otherwise, it is nil. + SystemMessage string `msgpack:"system_message,omitempty"` +} + +// Downstream information. +type Downstream struct { + // Status is replication status of the connection with the instance. + Status string `msgpack:"status"` + // Idle is the time (in seconds) since the last event was received. + Idle float64 `msgpack:"idle"` + // VClock contains the vector clock, which is a table of ‘id, lsn’ pairs. + VClock map[int]uint64 `msgpack:"vclock"` + // Lag is the time difference between the local time of instance n, + // recorded when the event was received, and the local time at another master + // recorded when the event was written to the write-ahead log on that master. + Lag float64 `msgpack:"lag"` + // Message contains an error message in case of a degraded state; otherwise, it is nil. + Message string `msgpack:"message,omitempty"` + // SystemMessage contains an error message in case of a degraded state; otherwise, it is nil. + SystemMessage string `msgpack:"system_message,omitempty"` } // InfoResponse represents the response structure diff --git a/box/info_test.go b/box/info_test.go new file mode 100644 index 000000000..818555b69 --- /dev/null +++ b/box/info_test.go @@ -0,0 +1,126 @@ +package box + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/vmihailenco/msgpack/v5" +) + +func TestInfo(t *testing.T) { + id := 1 + cases := []struct { + Name string + Struct Info + Data map[string]interface{} + }{ + { + Name: "Case: base info struct", + Struct: Info{ + Version: "2.11.4-0-g8cebbf2cad", + ID: &id, + RO: false, + UUID: "69360e9b-4641-4ec3-ab51-297f46749849", + PID: 1, + Status: "running", + LSN: 8, + }, + Data: map[string]interface{}{ + "version": "2.11.4-0-g8cebbf2cad", + "id": 1, + "ro": false, + "uuid": "69360e9b-4641-4ec3-ab51-297f46749849", + "pid": 1, + "status": "running", + "lsn": 8, + }, + }, + { + Name: "Case: info struct with replication", + Struct: Info{ + Version: "2.11.4-0-g8cebbf2cad", + ID: &id, + RO: false, + UUID: "69360e9b-4641-4ec3-ab51-297f46749849", + PID: 1, + Status: "running", + LSN: 8, + Replication: map[int]Replication{ + 1: { + ID: 1, + UUID: "69360e9b-4641-4ec3-ab51-297f46749849", + LSN: 8, + }, + 2: { + ID: 2, + UUID: "75f5f5aa-89f0-4d95-b5a9-96a0eaa0ce36", + LSN: 0, + Upstream: Upstream{ + Status: "follow", + Idle: 2.4564633660484, + Peer: "other.tarantool:3301", + Lag: 0.00011920928955078, + Message: "'getaddrinfo: Name or service not known'", + SystemMessage: "Input/output error", + }, + Downstream: Downstream{ + Status: "follow", + Idle: 2.8306158290943, + VClock: map[int]uint64{1: 8}, + Lag: 0, + Message: "'unexpected EOF when reading from socket'", + SystemMessage: "Broken pipe", + }, + }, + }, + }, + Data: map[string]interface{}{ + "version": "2.11.4-0-g8cebbf2cad", + "id": 1, + "ro": false, + "uuid": "69360e9b-4641-4ec3-ab51-297f46749849", + "pid": 1, + "status": "running", + "lsn": 8, + "replication": map[interface{}]interface{}{ + 1: map[string]interface{}{ + "id": 1, + "uuid": "69360e9b-4641-4ec3-ab51-297f46749849", + "lsn": 8, + }, + 2: map[string]interface{}{ + "id": 2, + "uuid": "75f5f5aa-89f0-4d95-b5a9-96a0eaa0ce36", + "lsn": 0, + "upstream": map[string]interface{}{ + "status": "follow", + "idle": 2.4564633660484, + "peer": "other.tarantool:3301", + "lag": 0.00011920928955078, + "message": "'getaddrinfo: Name or service not known'", + "system_message": "Input/output error", + }, + "downstream": map[string]interface{}{ + "status": "follow", + "idle": 2.8306158290943, + "vclock": map[interface{}]interface{}{1: 8}, + "lag": 0, + "message": "'unexpected EOF when reading from socket'", + "system_message": "Broken pipe", + }, + }, + }, + }, + }, + } + for _, tc := range cases { + data, err := msgpack.Marshal(tc.Data) + require.NoError(t, err, tc.Name) + + var result Info + err = msgpack.Unmarshal(data, &result) + require.NoError(t, err, tc.Name) + + require.Equal(t, tc.Struct, result) + } +} diff --git a/box/tarantool_test.go b/box/tarantool_test.go index 3d638b5b5..515eac3d0 100644 --- a/box/tarantool_test.go +++ b/box/tarantool_test.go @@ -31,6 +31,12 @@ func validateInfo(t testing.TB, info box.Info) { require.NotEmpty(t, info.Version) // Check that pid parsed correctly. require.NotEqual(t, info.PID, 0) + + // Check replication is parsed correctly. + require.NotEmpty(t, info.Replication) + + // Check one replica uuid is equal system uuid. + require.Equal(t, info.UUID, info.Replication[1].UUID) } func TestBox_Sugar_Info(t *testing.T) { diff --git a/box/testdata/config.lua b/box/testdata/config.lua index f3ee1a7b2..061439aa1 100644 --- a/box/testdata/config.lua +++ b/box/testdata/config.lua @@ -10,4 +10,7 @@ box.schema.user.grant('test', 'execute', 'universe', nil, { if_not_exists = true -- Set listen only when every other thing is configured. box.cfg{ listen = os.getenv("TEST_TNT_LISTEN"), + replication = { + os.getenv("TEST_TNT_LISTEN"), + }, }