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:
parent
03b66c94f4
commit
52c80d652d
@ -47,6 +47,9 @@ if __name__ == '__main__':
|
|||||||
'-v', '--verbose', default=False, action="store_true",
|
'-v', '--verbose', default=False, action="store_true",
|
||||||
help="Show all shard ranges. By default, only the number of shard "
|
help="Show all shard ranges. By default, only the number of shard "
|
||||||
"ranges is displayed if there are many shards.")
|
"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()
|
options, args = parser.parse_args()
|
||||||
|
|
||||||
|
@ -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.')
|
'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,
|
def print_db_info_metadata(db_type, info, metadata, drop_prefixes=False,
|
||||||
verbose=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,
|
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'):
|
if db_type not in ('account', 'container'):
|
||||||
print("Unrecognized DB type: internal error")
|
print("Unrecognized DB type: internal error")
|
||||||
raise InfoSystemExit()
|
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
|
info['shard_ranges'] = sranges
|
||||||
print_db_info_metadata(
|
print_db_info_metadata(
|
||||||
db_type, info, broker.metadata, drop_prefixes, verbose)
|
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:
|
try:
|
||||||
ring = Ring(swift_dir, ring_name=db_type)
|
ring = Ring(swift_dir, ring_name=db_type)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -724,22 +724,26 @@ class DatabaseBroker(object):
|
|||||||
return -1
|
return -1
|
||||||
return row['sync_point']
|
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.
|
Get a serialized copy of the sync table.
|
||||||
|
|
||||||
:param incoming: if True, get the last incoming sync, otherwise get
|
:param incoming: if True, get the last incoming sync, otherwise get
|
||||||
the last outgoing sync
|
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:
|
with self.get() as conn:
|
||||||
|
columns = 'remote_id, sync_point'
|
||||||
|
if include_timestamp:
|
||||||
|
columns += ', updated_at'
|
||||||
curs = conn.execute('''
|
curs = conn.execute('''
|
||||||
SELECT remote_id, sync_point FROM %s_sync
|
SELECT %s FROM %s_sync
|
||||||
''' % ('incoming' if incoming else 'outgoing'))
|
''' % (columns, 'incoming' if incoming else 'outgoing'))
|
||||||
result = []
|
curs.row_factory = dict_factory
|
||||||
for row in curs:
|
return [r for r in curs]
|
||||||
result.append({'remote_id': row[0], 'sync_point': row[1]})
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_max_row(self, table=None):
|
def get_max_row(self, table=None):
|
||||||
if not table:
|
if not table:
|
||||||
|
@ -29,7 +29,7 @@ from swift.common.storage_policy import StoragePolicy, POLICIES
|
|||||||
from swift.cli.info import (print_db_info_metadata, print_ring_locations,
|
from swift.cli.info import (print_db_info_metadata, print_ring_locations,
|
||||||
print_info, print_obj_metadata, print_obj,
|
print_info, print_obj_metadata, print_obj,
|
||||||
InfoSystemExit, print_item_locations,
|
InfoSystemExit, print_item_locations,
|
||||||
parse_get_node_args)
|
parse_get_node_args, print_db_syncs)
|
||||||
from swift.account.server import AccountController
|
from swift.account.server import AccountController
|
||||||
from swift.container.server import ContainerController
|
from swift.container.server import ContainerController
|
||||||
from swift.container.backend import UNSHARDED, SHARDED
|
from swift.container.backend import UNSHARDED, SHARDED
|
||||||
@ -666,6 +666,40 @@ Shard Ranges (3):
|
|||||||
self.assertIn(exp_cont_msg, out.getvalue())
|
self.assertIn(exp_cont_msg, out.getvalue())
|
||||||
self.assertIn(exp_obj_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):
|
def test_print_item_locations_account_container_object_dashed_ring(self):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
account = 'account'
|
account = 'account'
|
||||||
|
@ -21,6 +21,8 @@ import unittest
|
|||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from shutil import rmtree, copy
|
from shutil import rmtree, copy
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import mock
|
||||||
import six.moves.cPickle as pickle
|
import six.moves.cPickle as pickle
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
@ -1043,6 +1045,55 @@ class TestDatabaseBroker(TestDbBase):
|
|||||||
self.assertEqual(broker.get_items_since(3, 2), [])
|
self.assertEqual(broker.get_items_since(3, 2), [])
|
||||||
self.assertEqual(broker.get_items_since(999, 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):
|
def test_get_sync(self):
|
||||||
broker = DatabaseBroker(self.db_path)
|
broker = DatabaseBroker(self.db_path)
|
||||||
broker.db_type = 'test'
|
broker.db_type = 'test'
|
||||||
|
Loading…
Reference in New Issue
Block a user