syncbase.js support for syncgroups
Change-Id: I520eb340424115000f8aec1483e53041b7047842
diff --git a/src/nosql/database.js b/src/nosql/database.js
index 236eb77..1329135 100644
--- a/src/nosql/database.js
+++ b/src/nosql/database.js
@@ -8,6 +8,7 @@
var BatchDatabase = require('./batch-database');
var nosqlVdl = require('../gen-vdl/v.io/syncbase/v23/services/syncbase/nosql');
+var SyncGroup = require('./syncgroup');
var Table = require('./table');
var util = require('../util');
@@ -198,3 +199,21 @@
return cb(null, new BatchDatabase(db));
});
};
+
+/**
+ * Gets a handle to the SyncGroup with the given name.
+ *
+ * @param {string} name SyncGroup name.
+ */
+Database.prototype.syncGroup = function(name) {
+ return new SyncGroup(this, name);
+};
+
+/**
+ * Gets the global names of all SyncGroups attached to this database.
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {function} cb Callback.
+ */
+Database.prototype.getSyncGroupNames = function(ctx, cb) {
+ this._wire(ctx).getSyncGroupNames(ctx, cb);
+};
diff --git a/src/nosql/index.js b/src/nosql/index.js
index c0fe714..c589058 100644
--- a/src/nosql/index.js
+++ b/src/nosql/index.js
@@ -17,5 +17,7 @@
BatchOptions: vdl.BatchOptions,
ReadOnlyBatchError: vdl.ReadOnlyBatchError,
rowrange: rowrange,
- runInBatch: runInBatch
+ runInBatch: runInBatch,
+ SyncGroupMemberInfo: vdl.SyncGroupMemberInfo,
+ SyncGroupSpec: vdl.SyncGroupSpec
};
diff --git a/src/nosql/syncgroup.js b/src/nosql/syncgroup.js
new file mode 100644
index 0000000..2fdce27
--- /dev/null
+++ b/src/nosql/syncgroup.js
@@ -0,0 +1,151 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+module.exports = SyncGroup;
+
+/**
+ * SyncGroup is the interface for a SyncGroup in the store.
+ */
+function SyncGroup(db, name) {
+ if (!(this instanceof SyncGroup)) {
+ return new SyncGroup(db, name);
+ }
+
+ /**
+ * @private
+ */
+ Object.defineProperty(this, '_db', {
+ enumerable: false,
+ value: db,
+ writable: false
+ });
+
+ /**
+ * @property name
+ * @type {string}
+ */
+ Object.defineProperty(this, 'name', {
+ enumerable: true,
+ value: name,
+ writable: false
+ });
+}
+
+/**
+ * Creates a new SyncGroup with the given spec.
+ *
+ * Requires: Client must have at least Read access on the Database; prefix ACL
+ * must exist at each SyncGroup prefix; Client must have at least Read access
+ * on each of these prefix ACLs.
+ *
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {module:syncbase.nosql.SyncGroupSpec} spec SyncGroupSpec.
+ * @param {module:syncbase.nosql.SyncGroupMemberInfo} myInfo
+ * SyncGroupMemberInfo.
+ * @param {function} cb Callback.
+ */
+SyncGroup.prototype.create = function(ctx, spec, myInfo, cb) {
+ this._db._wire(ctx).createSyncGroup(ctx, this.name, spec, myInfo, cb);
+};
+
+/**
+ * Joins a SyncGroup.
+ *
+ * Requires: Client must have at least Read access on the Database and on the
+ * SyncGroup ACL.
+ *
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {module:syncbase.nosql.SyncGroupMemberInfo} myInfo
+ * SyncGroupMemberInfo.
+ * @param {function} cb Callback.
+ */
+SyncGroup.prototype.join = function(ctx, myInfo, cb) {
+ this._db._wire(ctx).joinSyncGroup(ctx, this.name, myInfo, cb);
+};
+
+/**
+ * Leaves the SyncGroup. Previously synced data will continue to be
+ * available.
+ *
+ * Requires: Client must have at least Read access on the Database.
+ *
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {function} cb Callback.
+ */
+SyncGroup.prototype.leave = function(ctx, cb) {
+ this._db._wire(ctx).leaveSyncGroup(ctx, this.name, cb);
+};
+
+/**
+ * Destroys a SyncGroup. Previously synced data will continue to be available
+ * to all members.
+ *
+ * Requires: Client must have at least Read access on the Database, and must
+ * have Admin access on the SyncGroup ACL.
+ *
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {function} cb Callback.
+ */
+SyncGroup.prototype.destroy = function(ctx, cb) {
+ this._db._wire(ctx).destroySyncGroup(ctx, this.name, cb);
+};
+
+/**
+ * Ejects a member from the SyncGroup. The ejected member will not be able to
+ * sync further, but will retain any data it has already synced.
+ *
+ * Requires: Client must have at least Read access on the Database, and must
+ * have Admin access on the SyncGroup ACL.
+ *
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {module:syncbase.nosql.SyncGroupMemberInfo} member
+ * SyncGroupMemberInfo.
+ * @param {function} cb Callback.
+ */
+SyncGroup.prototype.eject = function(ctx, member, cb) {
+ this._db._wire(ctx).ejectFromSyncGroup(ctx, this.name, member, cb);
+};
+
+/**
+ *
+ * Gets the SyncGroup spec. version allows for atomic read-modify-write of the
+ * spec - see comment for setSpec.
+ *
+ * Requires: Client must have at least Read access on the Database and on the
+ * SyncGroup ACL.
+ *
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {function} cb Callback.
+ */
+SyncGroup.prototype.getSpec = function(ctx, cb) {
+ this._db._wire(ctx).getSyncGroupSpec(ctx, this.name, cb);
+};
+
+/**
+ * Sets the SyncGroup spec. version may be either empty or the value from a
+ * previous Get. If not empty, Set will only succeed if the current version
+ * matches the specified one.
+ *
+ * Requires: Client must have at least Read access on the Database, and must
+ * have Admin access on the SyncGroup ACL.
+ *
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {module:syncbase.nosql.SyncGroupSpec} spec SyncGroupSpec.
+ * @param {function} cb Callback.
+ */
+SyncGroup.prototype.setSpec = function(ctx, spec, cb) {
+ this._db._wire(ctx).setSyncGroupSpec(ctx, this.name, spec, cb);
+};
+
+/**
+ * Gets the info objects for members of the SyncGroup.
+ *
+ * Requires: Client must have at least Read access on the Database and on the
+ * SyncGroup ACL.
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {function} cb Callback.
+ */
+SyncGroup.prototype.getMembers = function(ctx, cb) {
+ this._db._wire(ctx).getSyncGroupMembers(ctx, this.name, cb);
+};
diff --git a/test/integration/test-syncgroup.js b/test/integration/test-syncgroup.js
new file mode 100644
index 0000000..76cae4a
--- /dev/null
+++ b/test/integration/test-syncgroup.js
@@ -0,0 +1,247 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+var async = require('async');
+var naming = require('vanadium').naming;
+var test = require('prova');
+
+var nosql = require('../..').nosql;
+var SyncGroup = require('../../src/nosql/syncgroup');
+var verror = require('vanadium').verror;
+
+var testUtil = require('./util');
+var setupDatabase = testUtil.setupDatabase;
+var setupSyncGroup = testUtil.setupSyncGroup;
+var uniqueName = testUtil.uniqueName;
+
+// TODO(nlacasse): Where does this magic number 8 come from? It's in
+// syncgroup_test.go.
+var myInfo = new nosql.SyncGroupMemberInfo({
+ syncPriority: 8
+});
+
+test('db.syncGroup returns a SyncGroup with name', function(t) {
+ setupDatabase(t, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var db = o.database;
+
+ var sgName = uniqueName('syncgroup');
+ var sg = db.syncGroup(sgName);
+ t.ok(sg instanceof SyncGroup, 'syncgroup is instanceof SyncGroup');
+ t.equal(sg.name, sgName, 'syncgroup has correct name');
+ o.teardown(t.end);
+ });
+});
+
+test('syncgroup.create with empty spec', function(t) {
+ setupDatabase(t, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var db = o.database;
+ var ctx = o.ctx;
+
+ var spec = new nosql.SyncGroupSpec();
+ var name = uniqueName('syncgroup');
+
+ db.syncGroup(name).create(ctx, spec, myInfo, function(err) {
+ t.ok(err, 'should error');
+ t.ok(err instanceof verror.BadArgError, 'err is BadArgError');
+ o.teardown(t.end);
+ });
+ });
+});
+
+test('syncgroup.create with valid spec', function(t) {
+ setupDatabase(t, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var db = o.database;
+ var ctx = o.ctx;
+
+ // TODO(nlacasse): It's not obvious that the syncgroup name needs to be
+ // appended to a syncbase service name.
+ var name = naming.join(o.service.fullName, uniqueName('syncgroup'));
+ var prefix = 't1/foo';
+
+ var spec = new nosql.SyncGroupSpec({
+ description: 'test syncgroup ' + name,
+ perms: {},
+ prefixes: [prefix]
+ });
+
+ db.syncGroup(name).create(ctx, spec, myInfo, function(err) {
+ t.error(err, 'should not error');
+ o.teardown(t.end);
+ });
+ });
+});
+
+test('creating a nested syncgroup', function(t) {
+ var perms = {};
+ var prefixes = ['t1/foo'];
+
+ setupSyncGroup(t, perms, prefixes, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var db = o.database;
+ var ctx = o.ctx;
+
+ var prefixes2 = ['t1/foobar'];
+
+ // TODO(nlacasse): It's not obvious that the syncgroup name needs to be
+ // appended to a syncbase service name.
+ var name = naming.join(o.service.fullName, uniqueName('syncgroup'));
+
+ var spec = new nosql.SyncGroupSpec({
+ description: 'another syncgroup named ' + name,
+ perms: {},
+ prefixes: prefixes2
+ });
+
+ var sg2 = db.syncGroup(name);
+ sg2.create(ctx, spec, myInfo, function(err) {
+ t.error(err, 'should not error');
+ o.teardown(t.end);
+ });
+ });
+});
+
+test('creating a syncgroup that already exists', function(t) {
+ var perms = {};
+ var prefixes = ['t1/foo'];
+
+ setupSyncGroup(t, perms, prefixes, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var db = o.database;
+ var ctx = o.ctx;
+
+ var name = o.syncgroup.name;
+
+ var spec = new nosql.SyncGroupSpec({
+ description: 'another syncgroup named ' + name,
+ perms: {},
+ prefixes: ['another/prefix']
+ });
+
+ var sg2 = db.syncGroup(name);
+ sg2.create(ctx, spec, myInfo, function(err) {
+ t.ok(err, 'should error');
+ t.ok(err instanceof verror.ExistError, 'err is ExistError');
+ o.teardown(t.end);
+ });
+ });
+});
+
+test('syncgroup.join succeeds if user has Read access', function(t) {
+ var perms = new Map([
+ ['Read', {
+ 'in': ['...']
+ }]
+ ]);
+ var prefixes = ['t1/foo'];
+
+ setupSyncGroup(t, perms, prefixes, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ o.syncgroup.join(o.ctx, myInfo, function(err) {
+ t.error(err, 'should not error');
+ o.teardown(t.end);
+ });
+ });
+});
+
+test('syncgroup.join fails if user does not have Read access', function(t) {
+ var perms = new Map([
+ ['Read', {
+ 'in': ['some/blessing/name']
+ }]
+ ]);
+ var prefixes = ['t1/foo'];
+
+ setupSyncGroup(t, perms, prefixes, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var sg = o.syncgroup;
+ var ctx = o.ctx;
+
+ sg.join(ctx, myInfo, function(err) {
+ t.ok(err, 'should not error');
+ t.ok(err instanceof verror.NoAccessError, 'err is NoAccessError');
+ o.teardown(t.end);
+ });
+ });
+});
+
+// TODO(nlacasse): Enable this test once Syncbase server implements
+// Database.GetSyncGroupNames.
+test.skip('db.getSyncGroupNames returns the correct names', function(t) {
+ setupDatabase(t, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var ctx = o.ctx;
+ var db = o.database;
+
+ var names = [
+ uniqueName('syncgroup'),
+ uniqueName('syncgroup'),
+ uniqueName('syncgroup')
+ ];
+
+ var fullNames = names.map(function(name) {
+ return naming.join(o.service.fullName, name);
+ });
+
+ createSyncGroups();
+
+ function createSyncGroups() {
+ async.forEach(fullNames, function(fullName, cb) {
+ var spec = new nosql.SyncGroupSpec({
+ description: 'syncgroup named ' + fullName,
+ prefixes: ['']
+ });
+
+ db.syncGroup(fullName).create(ctx, spec, myInfo, cb);
+ }, getSyncGroupNames);
+ }
+
+ function getSyncGroupNames(err) {
+ if (err) {
+ t.error(err);
+ o.teardown(t.end);
+ }
+
+ db.getSyncGroupNames(ctx, assertSyncGroupNames);
+ }
+
+ function assertSyncGroupNames(err, gotNames) {
+ if (err) {
+ t.error(err);
+ return o.teardown(t.end);
+ }
+
+ fullNames.sort();
+ gotNames.sort();
+ t.deepEqual(fullNames, gotNames, 'syncgroup names are correct');
+ o.teardown(t.end);
+ }
+ });
+});
diff --git a/test/integration/util.js b/test/integration/util.js
index 6f8d934..01a2aa4 100644
--- a/test/integration/util.js
+++ b/test/integration/util.js
@@ -10,6 +10,7 @@
setupApp: setupApp,
setupDatabase: setupDatabase,
setupService: setupService,
+ setupSyncGroup: setupSyncGroup,
setupTable: setupTable,
assertScanRows: assertScanRows,
@@ -27,7 +28,7 @@
var SERVICE_NAME = require('./service-name');
// Helper function to generate unique names.
-var nameCounter = 0;
+var nameCounter = Date.now();
function uniqueName(prefix) {
prefix = prefix || 'name';
@@ -103,6 +104,42 @@
});
}
+// Initializes Vanadium runtime and creats an App, Database and SyncGroup.
+function setupSyncGroup(t, perms, prefixes, cb) {
+ setupDatabase(t, function(err, o) {
+ if (err) {
+ return cb(err);
+ }
+
+ var sgName = uniqueName('syncgroup');
+ var fullSgName = vanadium.naming.join(o.service.fullName, sgName);
+
+ // TODO(nlacasse): Where does this magic number 8 come from? It's in
+ // syncgroup_test.go.
+ var myInfo = new syncbase.nosql.SyncGroupMemberInfo({
+ syncPriority: 8
+ });
+
+ var spec = new syncbase.nosql.SyncGroupSpec({
+ description: 'test syncgroup ' + fullSgName,
+ perms: perms,
+ prefixes: prefixes
+ });
+
+ var sg = o.database.syncGroup(fullSgName);
+ sg.create(o.ctx, spec, myInfo, function(err) {
+ if (err) {
+ o.rt.close(t.error);
+ return cb(err);
+ }
+
+ return cb(null, extend(o, {
+ syncgroup: sg
+ }));
+ });
+ });
+}
+
// Initializes Vanadium runtime and creates an App, a Database and a Table.
function setupTable(t, cb) {
setupDatabase(t, function(err, o) {
diff --git a/test/start-syncbased.sh b/test/start-syncbased.sh
index 8fe21d6..d6c2e11 100755
--- a/test/start-syncbased.sh
+++ b/test/start-syncbased.sh
@@ -10,4 +10,4 @@
# fix service-runner to allow flags/arguments, and then have it start syncbased
# directly with the appropriate flags. Then we can delete this file.
-syncbased -v=3 --name test/syncbased --engine memstore
+syncbased -v=1 --name test/syncbased --engine memstore