cli: add --sync to db info to show syncs

When looking at containers and accounts it's sometimes nice to know who
they've been replicating with. This patch adds a `--sync|-s` option to
swift-{container|account}-info which will also dump the incoming and
outgoing sync tables:

  $ swift-container-info /srv/node3/sdb3/containers/294/624/49b9ff074c502ec5e429e7af99a30624/49b9ff074c502ec5e429e7af99a30624.db -s
  Path: /AUTH_test/new
    Account: AUTH_test
    Container: new
    Deleted: False
    Container Hash: 49b9ff074c502ec5e429e7af99a30624
  Metadata:
    Created at: 2022-02-16T05:34:05.988480 (1644989645.98848)
    Put Timestamp: 2022-02-16T05:34:05.981320 (1644989645.98132)
    Delete Timestamp: 1970-01-01T00:00:00.000000 (0)
    Status Timestamp: 2022-02-16T05:34:05.981320 (1644989645.98132)
    Object Count: 1
    Bytes Used: 7
    Storage Policy: default (0)
    Reported Put Timestamp: 1970-01-01T00:00:00.000000 (0)
    Reported Delete Timestamp: 1970-01-01T00:00:00.000000 (0)
    Reported Object Count: 0
    Reported Bytes Used: 0
    Chexor: 962368324c2ca023c56669d03ed92807
    UUID: f33184e7-56d5-4c74-9d2e-5417c187d722-sdb3
    X-Container-Sync-Point2: -1
    X-Container-Sync-Point1: -1
  No system metadata found in db file
  No user metadata found in db file
  Sharding Metadata:
    Type: root
    State: unsharded
  Incoming Syncs:
    Sync Point	Remote ID                                	Updated At
    1         	ce7268a1-f5d0-4b83-b993-af17b602a0ff-sdb1	2022-02-16T05:38:22.000000 (1644989902)
    1         	2af5abc0-7f70-4e2f-8f94-737aeaada7f4-sdb4	2022-02-16T05:38:22.000000 (1644989902)
  Outgoing Syncs:
    Sync Point	Remote ID	Updated At
  Partition	294
  Hash     	49b9ff074c502ec5e429e7af99a30624

As a follow up to the device in DB ID patch we can see that the replicas
at sdb1 and sdb4 have replicated with this node.

Change-Id: I23d786e82c6710bea7660a9acf8bbbd113b5b727
This commit is contained in:
Matthew Oliver 2022-02-16 16:38:52 +11:00 committed by Tim Burke
parent 03b66c94f4
commit 52c80d652d
5 changed files with 129 additions and 10 deletions

View File

@ -47,6 +47,9 @@ if __name__ == '__main__':
'-v', '--verbose', default=False, action="store_true",
help="Show all shard ranges. By default, only the number of shard "
"ranges is displayed if there are many shards.")
parser.add_option(
'--sync', '-s', default=False, action="store_true",
help="Output the contents of the incoming/outging sync tables")
options, args = parser.parse_args()

View File

@ -198,6 +198,28 @@ def print_ring_locations(ring, datadir, account, container=None, obj=None,
'real value is set in the config file on each storage node.')
def get_max_len_sync_item(syncs, item, title):
def map_func(element):
return str(element[item])
return max(list(map(len, map(map_func, syncs))) + [len(title)])
def print_db_syncs(incoming, syncs):
max_sync_point_len = get_max_len_sync_item(syncs, 'sync_point',
"Sync Point")
max_remote_len = get_max_len_sync_item(syncs, 'remote_id', "Remote ID")
print('%s Syncs:' % ('Incoming' if incoming else 'Outgoing'))
print(' %s\t%s\t%s' % ("Sync Point".ljust(max_sync_point_len),
"Remote ID".ljust(max_remote_len),
"Updated At"))
for sync in syncs:
print(' %s\t%s\t%s (%s)' % (
str(sync['sync_point']).ljust(max_sync_point_len),
sync['remote_id'].ljust(max_remote_len),
Timestamp(sync['updated_at']).isoformat,
sync['updated_at']))
def print_db_info_metadata(db_type, info, metadata, drop_prefixes=False,
verbose=False):
"""
@ -439,7 +461,7 @@ def print_obj_metadata(metadata, drop_prefixes=False):
def print_info(db_type, db_file, swift_dir='/etc/swift', stale_reads_ok=False,
drop_prefixes=False, verbose=False):
drop_prefixes=False, verbose=False, sync=False):
if db_type not in ('account', 'container'):
print("Unrecognized DB type: internal error")
raise InfoSystemExit()
@ -473,6 +495,11 @@ def print_info(db_type, db_file, swift_dir='/etc/swift', stale_reads_ok=False,
info['shard_ranges'] = sranges
print_db_info_metadata(
db_type, info, broker.metadata, drop_prefixes, verbose)
if sync:
# Print incoming / outgoing sync tables.
for incoming in (True, False):
print_db_syncs(incoming, broker.get_syncs(incoming,
include_timestamp=True))
try:
ring = Ring(swift_dir, ring_name=db_type)
except Exception:

View File

@ -724,22 +724,26 @@ class DatabaseBroker(object):
return -1
return row['sync_point']
def get_syncs(self, incoming=True):
def get_syncs(self, incoming=True, include_timestamp=False):
"""
Get a serialized copy of the sync table.
:param incoming: if True, get the last incoming sync, otherwise get
the last outgoing sync
:returns: list of {'remote_id', 'sync_point'}
:param include_timestamp: If True include the updated_at timestamp
:returns: list of {'remote_id', 'sync_point'} or
{'remote_id', 'sync_point', 'updated_at'}
if include_timestamp is True.
"""
with self.get() as conn:
columns = 'remote_id, sync_point'
if include_timestamp:
columns += ', updated_at'
curs = conn.execute('''
SELECT remote_id, sync_point FROM %s_sync
''' % ('incoming' if incoming else 'outgoing'))
result = []
for row in curs:
result.append({'remote_id': row[0], 'sync_point': row[1]})
return result
SELECT %s FROM %s_sync
''' % (columns, 'incoming' if incoming else 'outgoing'))
curs.row_factory = dict_factory
return [r for r in curs]
def get_max_row(self, table=None):
if not table:

View File

@ -29,7 +29,7 @@ from swift.common.storage_policy import StoragePolicy, POLICIES
from swift.cli.info import (print_db_info_metadata, print_ring_locations,
print_info, print_obj_metadata, print_obj,
InfoSystemExit, print_item_locations,
parse_get_node_args)
parse_get_node_args, print_db_syncs)
from swift.account.server import AccountController
from swift.container.server import ContainerController
from swift.container.backend import UNSHARDED, SHARDED
@ -666,6 +666,40 @@ Shard Ranges (3):
self.assertIn(exp_cont_msg, out.getvalue())
self.assertIn(exp_obj_msg, out.getvalue())
def test_print_db_syncs(self):
# first the empty case
for incoming in (True, False):
out = StringIO()
with mock.patch('sys.stdout', out):
print_db_syncs(incoming, [])
if incoming:
exp_heading = 'Incoming Syncs:'
else:
exp_heading = 'Outgoing Syncs:'
exp_heading += '\n Sync Point\tRemote ID\tUpdated At'
self.assertIn(exp_heading, out.getvalue())
# now add some syncs
ts0 = utils.Timestamp(1)
ts1 = utils.Timestamp(2)
syncs = [{'sync_point': 0, 'remote_id': 'remote_0',
'updated_at': str(int(ts0))},
{'sync_point': 1, 'remote_id': 'remote_1',
'updated_at': str(int(ts1))}]
template_output = """%s:\n Sync Point\tRemote ID\tUpdated At
0 \tremote_0 \t%s (%s)
1 \tremote_1 \t%s (%s)
"""
for incoming in (True, False):
out = StringIO()
with mock.patch('sys.stdout', out):
print_db_syncs(incoming, syncs)
output = template_output % (
'Incoming Syncs' if incoming else 'Outgoing Syncs',
ts0.isoformat, str(int(ts0)), ts1.isoformat, str(int(ts1)))
self.assertEqual(output, out.getvalue())
def test_print_item_locations_account_container_object_dashed_ring(self):
out = StringIO()
account = 'account'

View File

@ -21,6 +21,8 @@ import unittest
from tempfile import mkdtemp
from shutil import rmtree, copy
from uuid import uuid4
import mock
import six.moves.cPickle as pickle
import base64
@ -1043,6 +1045,55 @@ class TestDatabaseBroker(TestDbBase):
self.assertEqual(broker.get_items_since(3, 2), [])
self.assertEqual(broker.get_items_since(999, 2), [])
def test_get_syncs(self):
broker = DatabaseBroker(self.db_path)
broker.db_type = 'test'
broker.db_contains_type = 'test'
uuid1 = str(uuid4())
def _initialize(conn, timestamp, **kwargs):
conn.execute('CREATE TABLE test (one TEXT)')
conn.execute('CREATE TABLE test_stat (id TEXT)')
conn.execute('INSERT INTO test_stat (id) VALUES (?)', (uuid1,))
conn.execute('INSERT INTO test (one) VALUES ("1")')
conn.commit()
pass
broker._initialize = _initialize
broker.initialize(normalize_timestamp('1'))
for incoming in (True, False):
# Can't mock out timestamp now, because the update_at in the sync
# tables are cuase by a trigger inside sqlite which uses it's own
# now method. So instead track the time before and after to make
# sure we're getting the right timestamps.
ts0 = Timestamp.now()
broker.merge_syncs([
{'sync_point': 0, 'remote_id': 'remote_0'},
{'sync_point': 1, 'remote_id': 'remote_1'}], incoming)
time.sleep(0.005)
broker.merge_syncs([
{'sync_point': 2, 'remote_id': 'remote_2'}], incoming)
ts1 = Timestamp.now()
expected_syncs = [{'sync_point': 0, 'remote_id': 'remote_0'},
{'sync_point': 1, 'remote_id': 'remote_1'},
{'sync_point': 2, 'remote_id': 'remote_2'}]
self.assertEqual(expected_syncs, broker.get_syncs(incoming))
# if we want the updated_at timestamps too then:
expected_syncs[0]['updated_at'] = mock.ANY
expected_syncs[1]['updated_at'] = mock.ANY
expected_syncs[2]['updated_at'] = mock.ANY
actual_syncs = broker.get_syncs(incoming, include_timestamp=True)
self.assertEqual(expected_syncs, actual_syncs)
# Note that most of the time, we expect these all to be ==
# but we've been known to see sizeable delays in the gate at times
self.assertTrue(all([
str(int(ts0)) <= s['updated_at'] <= str(int(ts1))
for s in actual_syncs]))
def test_get_sync(self):
broker = DatabaseBroker(self.db_path)
broker.db_type = 'test'