Skip to content

Commit 276aa7e

Browse files
authored
Merge pull request #94 from UCLA-IRL/sync
Adding sync into Repo
2 parents 2dcd229 + e38ef0c commit 276aa7e

21 files changed

Lines changed: 795 additions & 32 deletions

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,8 @@ venv.bak/
114114
.leveldb/
115115
.pytest_cache/
116116
repo.db
117-
NDN_Repo.egg-info/
117+
NDN_Repo.egg-info/
118+
119+
# tianyuan's helper
120+
feature.*
121+
testrepo.*

docs/src/specification/specification.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ Specification
88
Delete <delete>
99
Check <check>
1010
TCP bulk insert <tcp_bulk>
11-
Security <security>
12-
Joining SVS Sync (Draft) <sync>
11+
Sync Group Join <sync_join>
12+
Sync Group Leave <sync_leave>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Sync Join
2+
=========
3+
4+
The sync join protocol is used to command the repo to join a state vector sync group.
5+
6+
1. The repo subscribes to the topic ``/<repo_name>/sync/join``.
7+
8+
2. The client publishes a message to the topic ``/<repo_name>/sync/join``. The message payload is of type
9+
``RepoCommandParam``, containing one or more ``SyncParam`` with the following fields:
10+
11+
* ``sync_prefix``: The name prefix of the sync group to join.
12+
* ``register_prefix``: (Optional) The name prefix for the repo to register with the forwarder. This prefix must not
13+
be the same as ``sync_prefix``.
14+
* ``data_name_dedupe``: (Optional) If true, the repo will deduplicate data names in the sync group.
15+
* ``reset``: (Optional) If true, rebuild state vectors from the stored state vectors on the repo disk. This is useful
16+
if interests are sent for permanently unavailable data from an old vector.
17+
18+
3. The repo joins the sync group, saving sync information to disk.
19+
20+
* The repo will listen for state vector interests for the sync group. Then, to fetch any data, the repo will send
21+
interests following the SVSPS data naming convention. For more information, see the
22+
`specification page <https://named-data.github.io/StateVectorSync/Specification.html>`_ for State Vector
23+
Synchronization.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Sync Leave
2+
==========
3+
4+
The sync leave protocol is used to command the repo to leave the sync group. This command also removes any information
5+
about the sync group from repo storage.
6+
7+
1. The repo subscribes to the topic ``/<repo_name>/sync/leave``.
8+
9+
2. The client publishes a message to the topic ``/<repo_name>/sync/leave``. The message payload is of type
10+
``RepoCommandParam``, containing one or more ``SyncParam`` with the following field:
11+
12+
* ``sync_prefix``: The name prefix of the sync group to leave.
13+
14+
3. The repo leaves the sync group, removing sync information from disk. The repo no longer listens to the originally
15+
specified register prefix.
16+
17+
* Note that any already-stored data packets that were received prior to leaving the sync group are *not* deleted.

examples/leavesync.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import logging
4+
from ndn.app import NDNApp
5+
from ndn.encoding import Name
6+
from ndn.security import KeychainDigest
7+
from ndn_python_repo.clients import SyncClient
8+
import uuid
9+
10+
async def run_leave_sync_client(app: NDNApp, **kwargs):
11+
client = SyncClient(app=app, prefix=kwargs['client_prefix'], repo_name=kwargs['repo_name'])
12+
await client.leave_sync(sync_prefix=kwargs['sync_prefix'])
13+
app.shutdown()
14+
15+
16+
def main():
17+
parser = argparse.ArgumentParser(description='leavesync')
18+
parser.add_argument('-r', '--repo_name',
19+
required=True, help='Name of repo')
20+
parser.add_argument('--client_prefix', required=True,
21+
help='prefix of this client')
22+
parser.add_argument('--sync_prefix', required=True,
23+
help='The sync prefix repo should leave')
24+
args = parser.parse_args()
25+
26+
logging.basicConfig(format='[%(asctime)s]%(levelname)s:%(message)s',
27+
datefmt='%Y-%m-%d %H:%M:%S',
28+
level=logging.DEBUG)
29+
30+
app = NDNApp(face=None, keychain=KeychainDigest())
31+
try:
32+
app.run_forever(
33+
after_start=run_leave_sync_client(app, repo_name=Name.from_str(args.repo_name),
34+
client_prefix=Name.from_str(args.client_prefix),
35+
sync_prefix=Name.from_str(args.sync_prefix)))
36+
except FileNotFoundError:
37+
print('Error: could not connect to NFD.')
38+
39+
40+
if __name__ == '__main__':
41+
main()

examples/sync.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import logging
4+
from ndn.app import NDNApp
5+
from ndn.encoding import Name
6+
from ndn.security import KeychainDigest
7+
from ndn_python_repo.clients import SyncClient
8+
import uuid
9+
10+
11+
async def run_sync_client(app: NDNApp, **kwargs):
12+
client = SyncClient(app=app, prefix=kwargs['client_prefix'], repo_name=kwargs['repo_name'])
13+
await client.join_sync(sync_prefix=kwargs['sync_prefix'], register_prefix=kwargs['register_prefix'],
14+
data_name_dedupe=kwargs['data_name_dedupe'],
15+
reset=kwargs['reset'])
16+
app.shutdown()
17+
18+
19+
def main():
20+
parser = argparse.ArgumentParser(description='sync')
21+
parser.add_argument('-r', '--repo_name',
22+
required=True, help='Name of repo')
23+
parser.add_argument('--client_prefix', required=True,
24+
help='prefix of this client')
25+
parser.add_argument('--sync_prefix', required=True,
26+
help='The sync prefix repo should join')
27+
parser.add_argument('--register_prefix', required=False,
28+
help='The prefix repo should register')
29+
parser.add_argument('--data_name_dedupe', required=False, default=False,
30+
help='whether repo should dedupe the sync group in data naming')
31+
parser.add_argument('--reset', required=False, default=False,
32+
help='whether repo should reset the sync group')
33+
args = parser.parse_args()
34+
35+
logging.basicConfig(format='[%(asctime)s]%(levelname)s:%(message)s',
36+
datefmt='%Y-%m-%d %H:%M:%S',
37+
level=logging.DEBUG)
38+
39+
app = NDNApp(face=None, keychain=KeychainDigest())
40+
register_prefix = None
41+
if args.register_prefix:
42+
register_prefix = Name.from_str(args.register_prefix)
43+
try:
44+
app.run_forever(
45+
after_start=run_sync_client(app, repo_name=Name.from_str(args.repo_name),
46+
client_prefix=Name.from_str(args.client_prefix),
47+
sync_prefix=Name.from_str(args.sync_prefix),
48+
register_prefix=register_prefix,
49+
data_name_dedupe=args.data_name_dedupe,
50+
reset=args.reset))
51+
except FileNotFoundError:
52+
print('Error: could not connect to NFD.')
53+
54+
55+
if __name__ == '__main__':
56+
main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .getfile import GetfileClient
22
from .putfile import PutfileClient
33
from .delete import DeleteClient
4+
from .sync import SyncClient
45
from .command_checker import CommandChecker

ndn_python_repo/clients/sync.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# -----------------------------------------------------------------------------
2+
# NDN Repo putfile client.
3+
#
4+
# @Author jonnykong@cs.ucla.edu
5+
# susmit@cs.colostate.edu
6+
# @Date 2019-10-18
7+
# -----------------------------------------------------------------------------
8+
9+
import os
10+
import sys
11+
sys.path.insert(1, os.path.join(sys.path[0], '..'))
12+
13+
import asyncio as aio
14+
from .command_checker import CommandChecker
15+
from ..command import RepoCommandParam, SyncParam, EmbName, RepoStatCode
16+
from ..utils import PubSub
17+
import logging
18+
import multiprocessing
19+
from ndn.app import NDNApp
20+
from ndn.encoding import Name, NonStrictName, Component, Links
21+
import os
22+
import platform
23+
from hashlib import sha256
24+
from typing import Optional, List
25+
26+
class SyncClient(object):
27+
28+
def __init__(self, app: NDNApp, prefix: NonStrictName, repo_name: NonStrictName):
29+
"""
30+
A client to sync the repo.
31+
32+
:param app: NDNApp.
33+
:param prefix: NonStrictName. The name of this client
34+
:param repo_name: NonStrictName. Routable name to remote repo.
35+
"""
36+
self.app = app
37+
self.prefix = prefix
38+
self.repo_name = Name.normalize(repo_name)
39+
self.encoded_packets = {}
40+
self.pb = PubSub(self.app, self.prefix)
41+
self.pb.base_prefix = self.prefix
42+
43+
# https://bugs.python.org/issue35219
44+
if platform.system() == 'Darwin':
45+
os.environ['OBJC_DISABLE_INITIALIZE_FORK_SAFETY'] = 'YES'
46+
47+
async def join_sync(self, sync_prefix: NonStrictName, register_prefix: NonStrictName = None,
48+
data_name_dedupe: bool = False, reset: bool = False) -> bytes:
49+
50+
# construct insert cmd msg
51+
cmd_param = RepoCommandParam()
52+
cmd_sync = SyncParam()
53+
cmd_sync.sync_prefix = sync_prefix
54+
cmd_sync.register_prefix = register_prefix
55+
cmd_sync.data_name_dedupe = data_name_dedupe
56+
cmd_sync.reset = reset
57+
58+
cmd_param.sync_groups = [cmd_sync]
59+
cmd_param_bytes = bytes(cmd_param.encode())
60+
61+
# publish msg to repo's join topic
62+
await self.pb.wait_for_ready()
63+
is_success = await self.pb.publish(self.repo_name + Name.from_str('sync/join'), cmd_param_bytes)
64+
if is_success:
65+
logging.info('Published an join msg and was acknowledged by a subscriber')
66+
else:
67+
logging.info('Published an join msg but was not acknowledged by a subscriber')
68+
return sha256(cmd_param_bytes).digest()
69+
70+
async def leave_sync(self, sync_prefix: NonStrictName) -> bytes:
71+
# construct insert cmd msg
72+
cmd_param = RepoCommandParam()
73+
cmd_sync = SyncParam()
74+
cmd_sync.sync_prefix = sync_prefix
75+
cmd_param.sync_groups = [cmd_sync]
76+
cmd_param_bytes = bytes(cmd_param.encode())
77+
78+
# publish msg to repo's leave topic
79+
await self.pb.wait_for_ready()
80+
is_success = await self.pb.publish(self.repo_name + Name.from_str('sync/leave'), cmd_param_bytes)
81+
if is_success:
82+
logging.info('Published an leave msg and was acknowledged by a subscriber')
83+
else:
84+
logging.info('Published an leave msg but was not acknowledged by a subscriber')
85+
return sha256(cmd_param_bytes).digest()

ndn_python_repo/cmd/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,11 @@ async def async_main(app: NDNApp, config):
8383
pb = PubSub(app)
8484
read_handle = ReadHandle(app, storage, config)
8585
write_handle = WriteCommandHandle(app, storage, pb, read_handle, config)
86+
sync_handle = SyncCommandHandle(app, storage, pb, read_handle, config)
8687
delete_handle = DeleteCommandHandle(app, storage, pb, read_handle, config)
8788
tcp_bulk_insert_handle = TcpBulkInsertHandle(storage, read_handle, config)
8889

89-
repo = Repo(app, storage, read_handle, write_handle, delete_handle, tcp_bulk_insert_handle, config)
90+
repo = Repo(app, storage, read_handle, write_handle, delete_handle, sync_handle, tcp_bulk_insert_handle, config)
9091
await repo.listen()
9192

9293

ndn_python_repo/command/repo_commands.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"""
77
import ndn.encoding as enc
88

9-
__all__ = ['RepoTypeNumber', 'EmbName', 'ObjParam', 'RepoCommandParam', 'ObjStatus', 'RepoCommandRes',
9+
__all__ = ['RepoTypeNumber', 'EmbName', 'ObjParam', 'SyncParam', 'SyncStatus', 'RepoCommandParam', 'ObjStatus', 'RepoCommandRes',
1010
'RepeatedNames', 'RepoStatCode', 'RepoStatQuery']
1111

1212

@@ -22,7 +22,10 @@ class RepoTypeNumber:
2222
CHECK_PREFIX = 213
2323
OBJECT_PARAM = 301
2424
OBJECT_RESULT = 302
25-
25+
SYNC_PARAM = 401
26+
SYNC_RESULT = 402
27+
SYNC_DATA_NAME_DEDUPE = 403
28+
SYNC_RESET = 404
2629

2730
class RepoStatCode:
2831
# 100 has not been used by previous code, but defined and documented.
@@ -51,9 +54,17 @@ class ObjParam(enc.TlvModel):
5154
end_block_id = enc.UintField(RepoTypeNumber.END_BLOCK_ID)
5255
register_prefix = enc.ModelField(RepoTypeNumber.REGISTER_PREFIX, EmbName)
5356

57+
class SyncParam(enc.TlvModel):
58+
sync_prefix = enc.NameField()
59+
register_prefix = enc.NameField()
60+
data_name_dedupe = enc.BoolField(RepoTypeNumber.SYNC_DATA_NAME_DEDUPE)
61+
reset = enc.BoolField(RepoTypeNumber.SYNC_RESET)
62+
# forwarding_hint = enc.ModelField(RepoTypeNumber.FORWARDING_HINT, enc.Links)
63+
# sync_prefix = enc.ModelField(RepoTypeNumber.REGISTER_PREFIX, EmbName)
5464

5565
class RepoCommandParam(enc.TlvModel):
5666
objs = enc.RepeatedField(enc.ModelField(RepoTypeNumber.OBJECT_PARAM, ObjParam))
67+
sync_groups = enc.RepeatedField(enc.ModelField(RepoTypeNumber.SYNC_PARAM, SyncParam))
5768

5869

5970
class RepoStatQuery(enc.TlvModel):
@@ -67,9 +78,15 @@ class ObjStatus(enc.TlvModel):
6778
delete_num = enc.UintField(RepoTypeNumber.DELETE_NUM)
6879

6980

81+
class SyncStatus(enc.TlvModel):
82+
name = enc.NameField()
83+
status_code = enc.UintField(RepoTypeNumber.STATUS_CODE)
84+
insert_num = enc.UintField(RepoTypeNumber.INSERT_NUM)
85+
7086
class RepoCommandRes(enc.TlvModel):
7187
status_code = enc.UintField(RepoTypeNumber.STATUS_CODE)
7288
objs = enc.RepeatedField(enc.ModelField(RepoTypeNumber.OBJECT_RESULT, ObjStatus))
89+
sync_groups = enc.RepeatedField(enc.ModelField(RepoTypeNumber.SYNC_RESULT, ObjStatus))
7390

7491

7592
class RepeatedNames(enc.TlvModel):

0 commit comments

Comments
 (0)