Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@sugarlabs.org>2014-05-08 11:41:25 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2014-05-08 11:41:25 (GMT)
commit70c1b1a26a610c082a49bd5fb29e6dca2bf47943 (patch)
tree935412a227a5bcd63580010a65a15e5dcc5d3050
parent5ebeca1a965aa4028fde7a73c7baffbdba705ef5 (diff)
Start local node router for admin needs
-rw-r--r--TODO1
-rwxr-xr-xmisc/aslo-sync13
-rwxr-xr-xsugar-network-node93
-rw-r--r--sugar_network/node/auth.py23
-rw-r--r--sugar_network/node/routes.py9
-rw-r--r--sugar_network/node/slave.py4
-rw-r--r--sugar_network/toolkit/__init__.py8
-rw-r--r--sugar_network/toolkit/application.py18
-rw-r--r--sugar_network/toolkit/coroutine.py26
-rw-r--r--sugar_network/toolkit/http.py3
-rw-r--r--sugar_network/toolkit/router.py3
-rw-r--r--tests/__init__.py12
-rwxr-xr-xtests/units/node/node_routes.py25
-rwxr-xr-xtests/units/node/slave.py32
14 files changed, 195 insertions, 75 deletions
diff --git a/TODO b/TODO
index 92e2063..88de973 100644
--- a/TODO
+++ b/TODO
@@ -2,7 +2,6 @@
- do not return Context.releases while non-slave sync
- deliver spawn events only to local subscribers
- switch auth from WWW-AUTHENTICATE to mutual authentication over the HTTPS
-- restrict ACL.LOCAL routes only to localhost clients
- pull node changes periodically for checked-in contexts
- refount blobs on node and client sides to delete orphaned ones
- secure node-to-node sync
diff --git a/misc/aslo-sync b/misc/aslo-sync
index 141883e..53aa7a3 100755
--- a/misc/aslo-sync
+++ b/misc/aslo-sync
@@ -39,7 +39,7 @@ from sugar_network.toolkit import licenses, application, Option
DOWNLOAD_URL = 'http://download.sugarlabs.org/activities'
ASLO_AUTHOR = {'d26cef70447160f31a7497cc0320f23a4e383cc3': {
- 'order': 0, 'role': 1, 'name': 'Activity Library',
+ 'role': 1, 'name': 'Activity Library',
}}
ACTIVITIES_PATH = '/upload/activities'
SUGAR_GUID = 'sugar'
@@ -366,7 +366,7 @@ class Application(application.Application):
'message': self.get_i18n_field(content),
'vote': vote,
'author': {nickname: {
- 'order': 0, 'role': 3, 'name': fullname,
+ 'role': 3, 'name': fullname,
}},
})
@@ -402,7 +402,7 @@ class Application(application.Application):
fullname = nickname
updates[guid] = {
'author': {nickname: {
- 'order': 0, 'role': 3, 'name': fullname,
+ 'role': 3, 'name': fullname,
}},
'value': self.get_i18n_field(content),
'ctime': int(time.mktime(modified.timetuple())),
@@ -539,7 +539,7 @@ class Application(application.Application):
updates[version_id] = {
'author': {
nickname: {
- 'order': 0, 'role': 3, 'name': fullname,
+ 'role': 3, 'name': fullname,
},
},
'value': release,
@@ -612,7 +612,7 @@ class Application(application.Application):
tags.add(row[0])
authors = {}
- for order, (role, email, nickname, fullname) in enumerate(self.sqlexec(
+ for role, email, nickname, fullname in self.sqlexec(
"""
SELECT
addons_users.role,
@@ -626,14 +626,13 @@ class Application(application.Application):
addons_users.addon_id=%s
ORDER BY
position
- """ % addon_id)):
+ """ % addon_id):
if not nickname:
nickname = email.split('@')[0]
fullname = fullname.strip()
if not fullname:
fullname = nickname
authors[nickname] = {
- 'order': order,
'role': 3 if role == 5 else 1,
'name': fullname,
}
diff --git a/sugar-network-node b/sugar-network-node
index 0b997a0..fea0d01 100755
--- a/sugar-network-node
+++ b/sugar-network-node
@@ -19,35 +19,61 @@ import os
import locale
import gettext
import logging
-from os.path import exists, join
+from os.path import exists, join, isabs
from sugar_network.toolkit import coroutine
coroutine.inject()
from sugar_network import db, node, toolkit
-from sugar_network.node.auth import SugarAuth
+from sugar_network.node.auth import SugarAuth, RootAuth
from sugar_network.node import obs, master, slave, model
from sugar_network.toolkit.http import Connection
from sugar_network.toolkit.router import Router
from sugar_network.toolkit import application, Option, enforce
+backdoor = Option(
+ 'path to a UNIX socket to serve administrative API requests; '
+ 'the entry point is not authenticated and assumes root privileges, '
+ 'thus, make sure that --backdoor path is accessible only by admins; '
+ 'not absolute path will be prefixed by the --rundir',
+ default='backdoor', name='backdoor')
+
+http_logdir = Option(
+ 'path to a directory to log HTTP requests; '
+ 'not absolute path will be prefixed by the --logdir',
+ name='http-logdir')
+
gettext.textdomain('sugar-network')
class Application(application.Daemon):
jobs = coroutine.Pool()
- node = None
+ servers = []
- def ensure_run(self):
- if toolkit.cachedir.value and not exists(toolkit.cachedir.value):
- os.makedirs(toolkit.cachedir.value)
+ def prolog(self):
if not exists(node.data_root.value):
os.makedirs(node.data_root.value)
enforce(os.access(node.data_root.value, os.W_OK),
'No write access to %r directory', node.data_root.value)
+ for opt, dirname in [
+ (toolkit.cachedir, 'cache'),
+ (application.logdir, 'log'),
+ (application.rundir, 'run'),
+ ]:
+ if not opt.value:
+ opt.value = join(node.data_root.value, dirname)
+ if not exists(opt.value):
+ os.makedirs(opt.value)
+
+ if http_logdir.value and not isabs(http_logdir.value):
+ http_logdir.value = \
+ join(application.logdir.value, http_logdir.value)
+ if not isabs(backdoor.value):
+ backdoor.value = join(application.rundir.value, backdoor.value)
+
def run(self):
enforce(node.master_api.value, 'Option --master-api missed')
@@ -66,40 +92,65 @@ class Application(application.Daemon):
resources = slave.RESOURCES
logging.info('Start slave node')
volume = model.Volume(node.data_root.value, resources)
- node_routes = node_routes_class(node.master_api.value,
+ routes = node_routes_class(node.master_api.value,
volume=volume, auth=SugarAuth(node.data_root.value),
find_limit=node.find_limit.value)
self.jobs.spawn(volume.populate)
+ self.jobs.spawn(model.presolve, join(node.data_root.value, 'files'))
- logging.info('Listening for requests on %s:%s',
+ logging.info('Listen requests on %s:%s',
node.host.value, node.port.value)
- server = coroutine.WSGIServer((node.host.value, node.port.value),
- Router(node_routes), **ssl_args)
+ server = coroutine.WSGIServer(
+ (node.host.value, node.port.value), Router(routes),
+ http_log=_open_logfile('access'), **ssl_args)
self.jobs.spawn(server.serve_forever)
- self.accept()
+ self.servers.append(server)
+
+ logging.info('Listen admin requests on %s', backdoor.value)
+ sock = coroutine.listen_unix_socket(backdoor.value,
+ reuse_address=True, mode=0660)
+ routes = node_routes_class(node.master_api.value,
+ volume=volume, auth=RootAuth())
+ server = coroutine.WSGIServer(sock, Router(routes),
+ http_log=_open_logfile('backdoor'))
+ self.jobs.spawn(server.serve_forever)
+ self.servers.append(server)
+ self.accept()
try:
self.jobs.join()
finally:
volume.close()
+ os.unlink(backdoor.value)
def shutdown(self):
self.jobs.kill()
+ def reopen_logs(self):
+ application.Daemon.reopen_logs(self)
+ if http_logdir.value:
+ for server in self.servers:
+ if server.http_log is None:
+ continue
+ server.http_log.close()
+ server.http_log = file(server.http_log.name, 'a+')
+
@application.command(
'direct synchronization with master node',
name='online-sync')
def online_sync(self):
- self._ensure_instance().post(cmd='online-sync')
+ enforce(node.mode.value == 'slave', 'Node is not slave')
+ self._ensure_instance().post(cmd='online_sync')
@application.command(
'sneakernet synchronization with other nodes using files '
'placed to the specified directory',
args='PATH', name='offline-sync')
def offline_sync(self):
+ enforce(node.mode.value == 'slave', 'Node is not slave')
enforce(self.args, 'PATH was not specified')
path = self.args.pop(0)
- self._ensure_instance().post(cmd='offline-sync', path=path)
+ self._ensure_instance().post(cmd='offline_sync', path=path)
@application.command(
'resolve/presolve packages for all package-type Contexts',
@@ -123,15 +174,27 @@ class Application(application.Daemon):
def _ensure_instance(self):
enforce(self.check_for_instance(), 'Node is not started')
- return Connection('http://localhost:%s' %
- node.port.value, trust_env=False)
+ return Connection('file://' + backdoor.value)
+
+
+def _open_logfile(name):
+ if not http_logdir.value:
+ return None
+ if not exists(http_logdir.value):
+ os.makedirs(http_logdir.value)
+ return file(join(http_logdir.value, name + '.log'), 'a+')
locale.setlocale(locale.LC_ALL, '')
+# New defaults
+application.logdir.value = None
+application.rundir.value = None
+
Option.seek('main', application)
Option.seek('main', [toolkit.cachedir])
Option.seek('node', node)
+Option.seek('node', [http_logdir])
Option.seek('obs', obs)
Option.seek('db', db)
diff --git a/sugar_network/node/auth.py b/sugar_network/node/auth.py
index d14bde6..13a9608 100644
--- a/sugar_network/node/auth.py
+++ b/sugar_network/node/auth.py
@@ -62,14 +62,25 @@ class Principal(str):
@property
def cap_create_with_guid(self):
- return self._caps & 1
+ return self._caps & 2
@cap_create_with_guid.setter
def cap_create_with_guid(self, value):
if value:
- self._caps |= 1
+ self._caps |= 2
else:
- self._caps ^= 1
+ self._caps ^= 2
+
+ @property
+ def cap_admin(self):
+ return self._caps & 4
+
+ @cap_admin.setter
+ def cap_admin(self, value):
+ if value:
+ self._caps |= 4
+ else:
+ self._caps ^= 4
def __enter__(self):
self._backup = self._caps
@@ -141,3 +152,9 @@ class SugarAuth(object):
# TODO
return principal
+
+
+class RootAuth(object):
+
+ def logon(self, request):
+ return Principal('root', 0xFFFF)
diff --git a/sugar_network/node/routes.py b/sugar_network/node/routes.py
index 68beb65..79c4056 100644
--- a/sugar_network/node/routes.py
+++ b/sugar_network/node/routes.py
@@ -82,6 +82,9 @@ class NodeRoutes(db.Routes, FrontRoutes):
allowed = True
enforce(allowed, http.Forbidden, 'Authors only')
+ if op.acl & ACL.ADMIN:
+ enforce(this.principal.cap_admin, http.Forbidden, 'Admins only')
+
@postroute
def postroute(self, result, exception):
request = this.request
@@ -172,9 +175,9 @@ class NodeRoutes(db.Routes, FrontRoutes):
return solution
@route('GET', ['context', None], cmd='resolve',
- arguments={'requires': list, 'stability': list})
- def resolve(self):
- solution = self.solve()
+ arguments={'requires': list, 'stability': list, 'assume': list})
+ def resolve(self, assume=None):
+ solution = self.solve(assume)
return self.volume.blobs.get(solution[this.request.guid]['blob'])
@route('GET', [None, None], cmd='diff')
diff --git a/sugar_network/node/slave.py b/sugar_network/node/slave.py
index a1195ab..7bf29e2 100644
--- a/sugar_network/node/slave.py
+++ b/sugar_network/node/slave.py
@@ -57,7 +57,7 @@ class SlaveRoutes(NodeRoutes):
self._master_guid = urlsplit(master_api).netloc
self._master_api = master_api
- @route('POST', cmd='online_sync', acl=ACL.LOCAL,
+ @route('POST', cmd='online_sync', acl=ACL.AUTH | ACL.ADMIN,
arguments={'no_pull': bool})
def online_sync(self, no_pull=False):
conn = http.Connection(self._master_api)
@@ -70,7 +70,7 @@ class SlaveRoutes(NodeRoutes):
headers={'Transfer-Encoding': 'chunked'})
self._import(packets.decode(response.raw))
- @route('POST', cmd='offline_sync', acl=ACL.LOCAL)
+ @route('POST', cmd='offline_sync', acl=ACL.AUTH | ACL.ADMIN)
def offline_sync(self, path):
enforce(isabs(path), "Argument 'path' is not an absolute path")
diff --git a/sugar_network/toolkit/__init__.py b/sugar_network/toolkit/__init__.py
index bf80271..9cad5e0 100644
--- a/sugar_network/toolkit/__init__.py
+++ b/sugar_network/toolkit/__init__.py
@@ -34,7 +34,7 @@ BUFFER_SIZE = 1024 * 10
cachedir = Option(
'path to a directory to keep cached files; such files '
'might take considerable number of bytes',
- default='/var/cache/sugar-network', name='cachedir')
+ name='cachedir')
_logger = logging.getLogger('toolkit')
@@ -390,7 +390,7 @@ def unique_filename(root, filename):
class mkdtemp(str):
def __new__(cls, *args, **kwargs):
- if 'dir' not in kwargs:
+ if cachedir.value and 'dir' not in kwargs:
kwargs['dir'] = cachedir.value
if not exists(kwargs['dir']):
os.makedirs(kwargs['dir'])
@@ -431,7 +431,7 @@ def svg_to_png(data, w, h=None):
def TemporaryFile(*args, **kwargs):
- if 'dir' not in kwargs:
+ if cachedir.value and 'dir' not in kwargs:
kwargs['dir'] = cachedir.value
if not exists(kwargs['dir']):
os.makedirs(kwargs['dir'])
@@ -441,7 +441,7 @@ def TemporaryFile(*args, **kwargs):
class NamedTemporaryFile(object):
def __init__(self, *args, **kwargs):
- if 'dir' not in kwargs:
+ if cachedir.value and 'dir' not in kwargs:
kwargs['dir'] = cachedir.value
if not exists(kwargs['dir']):
os.makedirs(kwargs['dir'])
diff --git a/sugar_network/toolkit/application.py b/sugar_network/toolkit/application.py
index bc6b99c..670a2d3 100644
--- a/sugar_network/toolkit/application.py
+++ b/sugar_network/toolkit/application.py
@@ -175,8 +175,6 @@ class Application(object):
pass
def start(self):
- self._rundir = abspath(rundir.value or '/var/run/' + self.name)
-
cmd_name = self.args.pop(0)
try:
cmd = self._commands.get(cmd_name)
@@ -186,10 +184,11 @@ class Application(object):
logging.info('Load configuration from %s file(s)',
', '.join(Option.config_files))
- if cmd.options.get('keep_stdout') and not foreground.value:
- self._keep_stdout()
-
self.prolog()
+ self._rundir = abspath(rundir.value or '/var/run/' + self.name)
+
+ if cmd.options.get('keep_stdout') and not foreground.value:
+ self.reopen_logs()
exit(cmd() or 0)
except Exception:
printf.exception('%s %s', _('Aborted'), self.name)
@@ -214,9 +213,6 @@ class Application(object):
pid = None
return pid
- def ensure_run(self):
- pass
-
def ensure_pidfile(self):
if not exists(self._rundir):
os.makedirs(self._rundir)
@@ -237,7 +233,7 @@ class Application(object):
else:
print Option.help()
- def _keep_stdout(self):
+ def reopen_logs(self):
log_dir = abspath(logdir.value)
if not exists(log_dir):
os.makedirs(log_dir)
@@ -280,7 +276,6 @@ class Daemon(Application):
pass
time.sleep(.5)
- self.ensure_run()
if foreground.value:
self._launch()
else:
@@ -333,7 +328,7 @@ class Daemon(Application):
def sighup_cb():
logging.info('Reload %s on SIGHUP signal', self.name)
- self._keep_stdout()
+ self.reopen_logs()
coroutine.signal(signal.SIGINT, sigterm_cb, signal.SIGINT)
coroutine.signal(signal.SIGTERM, sigterm_cb, signal.SIGTERM)
@@ -343,7 +338,6 @@ class Daemon(Application):
try:
self.run()
finally:
- self.epilog()
os.unlink(pid_path)
def _daemonize(self):
diff --git a/sugar_network/toolkit/coroutine.py b/sugar_network/toolkit/coroutine.py
index 1f3d842..c3d4a47 100644
--- a/sugar_network/toolkit/coroutine.py
+++ b/sugar_network/toolkit/coroutine.py
@@ -80,13 +80,15 @@ def socket(*args, **kwargs):
return gevent.socket.socket(*args, **kwargs)
-def listen_unix_socket(path, backlog=5):
+def listen_unix_socket(path, backlog=5, reuse_address=False, mode=None):
# pylint: disable-msg=E1101
from tempfile import NamedTemporaryFile
import _socket
if exists(path):
- raise RuntimeError('The socket address is in use')
+ if not reuse_address:
+ raise RuntimeError('The socket address is in use')
+ os.unlink(path)
sock = socket(_socket.AF_UNIX, _socket.SOCK_STREAM)
sock.setblocking(0)
@@ -94,6 +96,8 @@ def listen_unix_socket(path, backlog=5):
with NamedTemporaryFile(dir=dirname(path)) as tmp_path:
pass
sock.bind(tmp_path.name)
+ if mode is not None:
+ os.chmod(tmp_path.name, mode)
try:
os.rename(tmp_path.name, path)
except Exception, error:
@@ -141,7 +145,11 @@ def Server(*args, **kwargs):
def WSGIServer(*args, **kwargs):
import gevent.wsgi
- class WSGIHandler(gevent.wsgi.WSGIHandler):
+ class Server(gevent.wsgi.WSGIServer):
+
+ http_log = kwargs.pop('http_log') if 'http_log' in kwargs else None
+
+ class Handler(gevent.wsgi.WSGIHandler):
def log_error(self, msg, *args):
if args:
@@ -149,14 +157,16 @@ def WSGIServer(*args, **kwargs):
_wsgi_logger.error('%s %s', self.format_request(), msg)
def log_request(self):
- _wsgi_logger.debug('%s', self.format_request())
+ logfile = server.http_log
+ if logfile is not None:
+ logfile.write(self.format_request())
+ logfile.write('\n')
kwargs['spawn'] = Pool()
if 'handler_class' not in kwargs:
- if logging.getLogger().level >= logging.DEBUG:
- WSGIHandler.log_request = lambda * args: None
- kwargs['handler_class'] = WSGIHandler
- return gevent.wsgi.WSGIServer(*args, **kwargs)
+ kwargs['handler_class'] = Handler
+ server = Server(*args, **kwargs)
+ return server
def Event():
diff --git a/sugar_network/toolkit/http.py b/sugar_network/toolkit/http.py
index f3c2ef8..51b34ee 100644
--- a/sugar_network/toolkit/http.py
+++ b/sugar_network/toolkit/http.py
@@ -119,6 +119,9 @@ class Connection(object):
self._session = None
self._auth_request = auth_request
+ if self.url and self.url.startswith('file://'):
+ self._session_args['trust_env'] = False
+
def __repr__(self):
return '<Connection url=%s>' % self.url
diff --git a/sugar_network/toolkit/router.py b/sugar_network/toolkit/router.py
index ba498d6..7b2186d 100644
--- a/sugar_network/toolkit/router.py
+++ b/sugar_network/toolkit/router.py
@@ -88,6 +88,7 @@ class ACL(object):
AGG_AUTHOR = 1 << 12
LOCAL = 1 << 20
+ ADMIN = 1 << 21
NAMES = {
CREATE: 'Create',
@@ -854,6 +855,8 @@ class _Route(object):
'ACL.AUTHOR requires longer path')
enforce(acl ^ ACL.AGG_AUTHOR or len(path) >= 3,
'ACL.AGG_AUTHOR requires longer path')
+ enforce(acl ^ ACL.ADMIN or acl & ACL.AUTH,
+ 'ACL.ADMIN without ACL.AUTH')
self.op = (method, cmd)
self.callback = callback
diff --git a/tests/__init__.py b/tests/__init__.py
index b890003..c096121 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -279,12 +279,14 @@ class Test(unittest.TestCase):
def create_mountset(self, classes=None):
self.start_server(classes, root=False)
- def start_master(self, classes=None, routes=MasterRoutes):
+ def start_master(self, classes=None, routes=MasterRoutes, auth=None):
if classes is None:
classes = master.RESOURCES
+ if auth is None:
+ auth = SugarAuth('master')
#self.touch(('master/etc/private/node', file(join(root, 'data', NODE_UID)).read()))
self.node_volume = NodeVolume('master', classes)
- self.node_routes = routes(node.master_api.value, volume=self.node_volume, auth=SugarAuth('master'))
+ self.node_routes = routes(node.master_api.value, volume=self.node_volume, auth=auth)
self.node_router = Router(self.node_routes)
self.node = coroutine.WSGIServer(('127.0.0.1', 7777), self.node_router)
coroutine.spawn(self.node.serve_forever)
@@ -293,15 +295,17 @@ class Test(unittest.TestCase):
this.call = self.node_router.call
return self.node_volume
- def fork_master(self, classes=None, routes=MasterRoutes, cb=None):
+ def fork_master(self, classes=None, routes=MasterRoutes, cb=None, auth=None):
if classes is None:
classes = master.RESOURCES
+ if auth is None:
+ auth = SugarAuth('master')
def _node():
volume = NodeVolume('master', classes)
if cb is not None:
cb(volume)
- anode = coroutine.WSGIServer(('127.0.0.1', 7777), Router(routes(node.master_api.value, volume=volume, auth=SugarAuth('master'))))
+ anode = coroutine.WSGIServer(('127.0.0.1', 7777), Router(routes(node.master_api.value, volume=volume, auth=auth)))
anode.serve_forever()
pid = self.fork(_node)
diff --git a/tests/units/node/node_routes.py b/tests/units/node/node_routes.py
index 9ebd117..bd29a57 100755
--- a/tests/units/node/node_routes.py
+++ b/tests/units/node/node_routes.py
@@ -99,6 +99,31 @@ class NodeRoutesTest(tests.Test):
this.call(method='GET', cmd='probe2', path=['document', guid], environ=auth_env(tests.UID2))
this.call(method='GET', cmd='probe2', path=['document', guid])
+ def test_CapAdmin(self):
+
+ class Routes(NodeRoutes):
+
+ def __init__(self, master_api, **kwargs):
+ NodeRoutes.__init__(self, 'node', **kwargs)
+
+ @route('PROBE', acl=ACL.AUTH | ACL.ADMIN)
+ def probe(self):
+ pass
+
+ class Auth(object):
+
+ caps = 0
+
+ def logon(self, request):
+ return Principal('user', Auth.caps)
+
+ volume = self.start_master([], Routes, auth=Auth())
+
+ Auth.caps = 0
+ self.assertRaises(http.Forbidden, this.call, method='PROBE')
+ Auth.caps = 0xFF
+ this.call(method='PROBE')
+
def test_ForbiddenCommandsForUserResource(self):
volume = self.start_master()
diff --git a/tests/units/node/slave.py b/tests/units/node/slave.py
index f7c149d..f00f0e1 100755
--- a/tests/units/node/slave.py
+++ b/tests/units/node/slave.py
@@ -14,7 +14,7 @@ from sugar_network.client import Connection
from sugar_network.node import master_api
from sugar_network.node.master import MasterRoutes
from sugar_network.node.slave import SlaveRoutes
-from sugar_network.node.auth import SugarAuth
+from sugar_network.node.auth import RootAuth
from sugar_network.node.model import User
from sugar_network.db.volume import Volume
from sugar_network.toolkit.router import Router, File
@@ -50,13 +50,13 @@ class SlaveTest(tests.Test):
self.Document = Document
self.slave_volume = Volume('slave', [User, Document])
- self.slave_routes = SlaveRoutes(master_api.value, volume=self.slave_volume, auth=SugarAuth('slave'))
+ self.slave_routes = SlaveRoutes(master_api.value, volume=self.slave_volume, auth=RootAuth())
self.slave_server = coroutine.WSGIServer(('127.0.0.1', 8888), Router(self.slave_routes))
coroutine.spawn(self.slave_server.serve_forever)
coroutine.dispatch()
def test_online_sync_Push(self):
- self.fork_master([User, self.Document])
+ self.fork_master([User, self.Document], auth=RootAuth())
master = Connection('http://127.0.0.1:7777')
slave = Connection('http://127.0.0.1:8888')
@@ -74,7 +74,7 @@ class SlaveTest(tests.Test):
],
master.get(['document'], reply=['guid', 'message'])['result'])
self.assertEqual([[2, None]], json.load(file('slave/var/pull')))
- self.assertEqual([[4, None]], json.load(file('slave/var/push')))
+ self.assertEqual([[3, None]], json.load(file('slave/var/push')))
guid3 = slave.post(['document'], {'message': '3', 'title': ''})
slave.post(cmd='online_sync')
@@ -85,14 +85,14 @@ class SlaveTest(tests.Test):
],
master.get(['document'], reply=['guid', 'message'])['result'])
self.assertEqual([[3, None]], json.load(file('slave/var/pull')))
- self.assertEqual([[5, None]], json.load(file('slave/var/push')))
+ self.assertEqual([[4, None]], json.load(file('slave/var/push')))
coroutine.sleep(1)
slave.put(['document', guid2], {'message': '22'})
slave.post(cmd='online_sync')
self.assertEqual('22', master.get(['document', guid2, 'message']))
self.assertEqual([[4, None]], json.load(file('slave/var/pull')))
- self.assertEqual([[6, None]], json.load(file('slave/var/push')))
+ self.assertEqual([[5, None]], json.load(file('slave/var/push')))
coroutine.sleep(1)
slave.delete(['document', guid1])
@@ -103,7 +103,7 @@ class SlaveTest(tests.Test):
],
master.get(['document'], reply=['guid', 'message'])['result'])
self.assertEqual([[5, None]], json.load(file('slave/var/pull')))
- self.assertEqual([[7, None]], json.load(file('slave/var/push')))
+ self.assertEqual([[6, None]], json.load(file('slave/var/push')))
coroutine.sleep(1)
slave.put(['document', guid2], {'message': 'b'})
@@ -117,10 +117,10 @@ class SlaveTest(tests.Test):
]),
sorted(master.get(['document'], reply=['guid', 'message'])['result']))
self.assertEqual([[6, None]], json.load(file('slave/var/pull')))
- self.assertEqual([[11, None]], json.load(file('slave/var/push')))
+ self.assertEqual([[10, None]], json.load(file('slave/var/push')))
def test_online_sync_Pull(self):
- self.fork_master([User, self.Document])
+ self.fork_master([User, self.Document], auth=RootAuth())
master = Connection('http://127.0.0.1:7777')
slave = Connection('http://127.0.0.1:8888')
@@ -138,7 +138,7 @@ class SlaveTest(tests.Test):
{'guid': guid2, 'message': '2'},
],
slave.get(['document'], reply=['guid', 'message'])['result'])
- self.assertEqual([[4, None]], json.load(file('slave/var/pull')))
+ self.assertEqual([[3, None]], json.load(file('slave/var/pull')))
self.assertEqual([[2, None]], json.load(file('slave/var/push')))
guid3 = master.post(['document'], {'message': '3', 'title': ''})
@@ -149,14 +149,14 @@ class SlaveTest(tests.Test):
{'guid': guid3, 'message': '3'},
],
slave.get(['document'], reply=['guid', 'message'])['result'])
- self.assertEqual([[5, None]], json.load(file('slave/var/pull')))
+ self.assertEqual([[4, None]], json.load(file('slave/var/pull')))
self.assertEqual([[3, None]], json.load(file('slave/var/push')))
coroutine.sleep(1)
master.put(['document', guid2], {'message': '22'})
slave.post(cmd='online_sync')
self.assertEqual('22', slave.get(['document', guid2, 'message']))
- self.assertEqual([[6, None]], json.load(file('slave/var/pull')))
+ self.assertEqual([[5, None]], json.load(file('slave/var/pull')))
self.assertEqual([[4, None]], json.load(file('slave/var/push')))
coroutine.sleep(1)
@@ -167,7 +167,7 @@ class SlaveTest(tests.Test):
{'guid': guid3, 'message': '3'},
],
slave.get(['document'], reply=['guid', 'message'])['result'])
- self.assertEqual([[7, None]], json.load(file('slave/var/pull')))
+ self.assertEqual([[6, None]], json.load(file('slave/var/pull')))
self.assertEqual([[5, None]], json.load(file('slave/var/push')))
coroutine.sleep(1)
@@ -181,11 +181,11 @@ class SlaveTest(tests.Test):
{'guid': guid4, 'message': 'd'},
],
slave.get(['document'], reply=['guid', 'message'])['result'])
- self.assertEqual([[11, None]], json.load(file('slave/var/pull')))
+ self.assertEqual([[10, None]], json.load(file('slave/var/pull')))
self.assertEqual([[6, None]], json.load(file('slave/var/push')))
def test_online_sync_PullBlobs(self):
- self.fork_master([User, self.Document])
+ self.fork_master([User, self.Document], auth=RootAuth())
master = Connection('http://127.0.0.1:7777')
slave = Connection('http://127.0.0.1:8888')
@@ -203,7 +203,7 @@ class SlaveTest(tests.Test):
self.assertEqual('file', file('slave/files/foo/bar').read())
def test_online_sync_PullFromPreviouslyMergedRecord(self):
- self.fork_master([User, self.Document])
+ self.fork_master([User, self.Document], auth=RootAuth())
master = Connection('http://127.0.0.1:7777')
slave = Connection('http://127.0.0.1:8888')