diff options
Diffstat (limited to 'jstests/libs')
-rw-r--r-- | jstests/libs/concurrent.js | 30 | ||||
-rw-r--r-- | jstests/libs/fun.js | 32 | ||||
-rw-r--r-- | jstests/libs/geo_near_random.js | 78 | ||||
-rw-r--r-- | jstests/libs/grid.js | 172 | ||||
-rw-r--r-- | jstests/libs/network.js | 37 |
5 files changed, 349 insertions, 0 deletions
diff --git a/jstests/libs/concurrent.js b/jstests/libs/concurrent.js new file mode 100644 index 0000000..9198818 --- /dev/null +++ b/jstests/libs/concurrent.js @@ -0,0 +1,30 @@ +/* NOTE: Requires mongo shell to be built with V8 javascript engine, +which implements concurrent threads via fork() */ + +// Fork and start +function fork_(thunk) { + thread = fork(thunk) + thread.start() + return thread +} + +// In functional form, useful for high-order functions like map in fun.js +function join_(thread) {thread.join()} + +// Fork a loop on each one-arg block and wait for all of them to terminate. Foreground blocks are executed n times, background blocks are executed repeatedly until all forground loops finish. If any fail, stop all loops and reraise exception in main thread +function parallel(n, foregroundBlock1s, backgroundBlock1s) { + var err = null + var stop = false + function loop(m) {return function(block1) {return function() { + for (var i = 0; i < m; i++) {if (stop) break; block1(i)} }}} + function watch(block) {return function() { + try {block()} catch(e) {err = e; stop = true}}} + foreThunks = map(watch, map(loop(n), foregroundBlock1s)) + backThunks = map(watch, map(loop(Infinity), backgroundBlock1s)) + foreThreads = map(fork_, foreThunks) + backThreads = map(fork_, backThunks) + map(join_, foreThreads) + stop = true + map(join_, backThreads) + if (err != null) throw err +} diff --git a/jstests/libs/fun.js b/jstests/libs/fun.js new file mode 100644 index 0000000..276f32a --- /dev/null +++ b/jstests/libs/fun.js @@ -0,0 +1,32 @@ +// General high-order functions + +function forEach (action, array) { + for (var i = 0; i < array.length; i++) + action (array[i]); +} + +function foldl (combine, base, array) { + for (var i = 0; i < array.length; i++) + base = combine (base, array[i]); + return base +} + +function foldr (combine, base, array) { + for (var i = array.length - 1; i >= 0; i--) + base = combine (array[i], base); + return base +} + +function map (func, array) { + var result = []; + for (var i = 0; i < array.length; i++) + result.push (func (array[i])); + return result +} + +function filter (pred, array) { + var result = [] + for (var i = 0; i < array.length; i++) + if (pred (array[i])) result.push (array[i]); + return result +} diff --git a/jstests/libs/geo_near_random.js b/jstests/libs/geo_near_random.js new file mode 100644 index 0000000..8624ef2 --- /dev/null +++ b/jstests/libs/geo_near_random.js @@ -0,0 +1,78 @@ +GeoNearRandomTest = function(name) { + this.name = name; + this.t = db[name]; + this.nPts = 0; + + // reset state + this.t.drop(); + Random.srand(1234); + + print("starting test: " + name); +} + + +GeoNearRandomTest.prototype.mkPt = function mkPt(scale){ + scale = scale || 1; // scale is good for staying away from edges + return [((Random.rand() * 359.8) - 179.9) * scale, ((Random.rand() * 180) - 90) * scale]; +} + +GeoNearRandomTest.prototype.insertPts = function(nPts) { + assert.eq(this.nPts, 0, "insertPoints already called"); + this.nPts = nPts; + + for (var i=0; i<nPts; i++){ + this.t.insert({_id: i, loc: this.mkPt()}); + } + + this.t.ensureIndex({loc: '2d'}); +} + +GeoNearRandomTest.prototype.assertIsPrefix = function(short, long) { + for (var i=0; i < short.length; i++){ + assert.eq(short[i], long[i]); + } +} + +GeoNearRandomTest.prototype.testPt = function(pt, opts) { + assert.neq(this.nPts, 0, "insertPoints not yet called"); + + opts = opts || {}; + opts['sphere'] = opts['sphere'] || 0; + opts['nToTest'] = opts['nToTest'] || this.nPts; // be careful, test is O( N^2 ) + + print("testing point: " + tojson(pt) + " opts: " + tojson(opts)); + + + var cmd = {geoNear:this.t.getName(), near: pt, num: 1, spherical:opts.sphere}; + + var last = db.runCommand(cmd).results; + for (var i=2; i <= opts.nToTest; i++){ + //print(i); // uncomment to watch status + cmd.num = i + var ret = db.runCommand(cmd).results; + + try { + this.assertIsPrefix(last, ret); + } catch (e) { + print("*** failed while compairing " + (i-1) + " and " + i); + printjson(cmd); + throw e; // rethrow + } + + last = ret; + } + + + if (!opts.sharded){ + last = last.map(function(x){return x.obj}); + + var query = {loc:{}}; + query.loc[ opts.sphere ? '$nearSphere' : '$near' ] = pt; + var near = this.t.find(query).limit(opts.nToTest).toArray(); + + this.assertIsPrefix(last, near); + assert.eq(last, near); + } +} + + diff --git a/jstests/libs/grid.js b/jstests/libs/grid.js new file mode 100644 index 0000000..7aef176 --- /dev/null +++ b/jstests/libs/grid.js @@ -0,0 +1,172 @@ +// Grid infrastructure: Servers, ReplicaSets, ConfigSets, Shards, Routers (mongos). Convenient objects and functions on top of those in shell/servers.js -Tony + +load('jstests/libs/fun.js') +load('jstests/libs/network.js') + +// New servers and routers take and increment port number from this. +// A comment containing FreshPorts monad implies reading and incrementing this, IO may also read/increment this. +var nextPort = 31000 + +/*** Server is the spec of a mongod, ie. all its command line options. + To start a server call 'begin' ***/ +// new Server :: String -> FreshPorts Server +function Server (name) { + this.dbpath = '/data/db/' + name + nextPort + this.port = nextPort++ + this.noprealloc = '' + this.smallfiles = '' + this.rest = '' + this.oplogSize = 8 +} + +Server.prototype.addr = '127.0.0.1' + +// Server -> String <addr:port> +Server.prototype.host = function() { + return this.addr + ':' + this.port +} + +// Start a new server with this spec and return connection to it +// Server -> IO Connection +Server.prototype.begin = function() { + return startMongodEmpty(this) +} + +// Stop server and remove db directory +// Server -> IO () +Server.prototype.end = function() { + print('Stopping mongod on port ' + this.port) + stopMongod (this.port) + resetDbpath (this.dbpath) +} + +// Cut server from network so it is unreachable (but still alive) +// Requires sudo access and ipfw program (Mac OS X and BSD Unix). TODO: use iptables on Linux. +function cutServer (conn) { + var addrport = parseHost (conn.host) + cutNetwork (addrport.port) +} + +// Ensure server is connected to network (undo cutServer) +// Requires sudo access and ipfw program (Mac OS X and BSD Unix). TODO: use iptables on Linux. +function uncutServer (conn) { + var iport = parseHost (conn.host) + restoreNetwork (iport.port) +} + +// Kill server process at other end of this connection +function killServer (conn, _signal) { + var signal = _signal || 15 + var iport = parseHost (conn.host) + stopMongod (iport.port, signal) +} + +/*** ReplicaSet is the spec of a replica set, ie. options given to ReplicaSetTest. + To start a replica set call 'begin' ***/ +// new ReplicaSet :: String -> Int -> FreshPorts ReplicaSet +function ReplicaSet (name, numServers) { + this.name = name + this.host = '127.0.0.1' + this.nodes = numServers + this.startPort = nextPort + this.oplogSize = 40 + nextPort += numServers +} + +// Start a replica set with this spec and return ReplSetTest, which hold connections to the servers including the master server. Call ReplicaSetTest.stopSet() to end all servers +// ReplicaSet -> IO ReplicaSetTest +ReplicaSet.prototype.begin = function() { + var rs = new ReplSetTest(this) + rs.startSet() + rs.initiate() + rs.awaitReplication() + return rs +} + +// Create a new server and add it to replica set +// ReplicaSetTest -> IO Connection +ReplSetTest.prototype.addServer = function() { + var conn = this.add() + nextPort++ + this.reInitiate() + this.awaitReplication() + assert.soon(function() { + var doc = conn.getDB('admin').isMaster() + return doc['ismaster'] || doc['secondary'] + }) + return conn +} + +/*** ConfigSet is a set of specs (Servers) for sharding config servers. + Supply either the servers or the number of servers desired. + To start the config servers call 'begin' ***/ +// new ConfigSet :: [Server] or Int -> FreshPorts ConfigSet +function ConfigSet (configSvrsOrNumSvrs) { + if (typeof configSvrsOrNumSvrs == 'number') { + this.configSvrs = [] + for (var i = 0; i < configSvrsOrNumSvrs; i++) + this.configSvrs.push (new Server ('config')) + } else + this.configSvrs = configSvrs +} + +// Start config servers, return list of connections to them +// ConfigSet -> IO [Connection] +ConfigSet.prototype.begin = function() { + return map (function(s) {return s.begin()}, this.configSvrs) +} + +// Stop config servers +// ConfigSet -> IO () +ConfigSet.prototype.end = function() { + return map (function(s) {return s.end()}, this.configSvrs) +} + +/*** Router is the spec for a mongos, ie, its command line options. + To start a router (mongos) call 'begin' ***/ +// new Router :: ConfigSet -> FreshPorts Router +function Router (configSet) { + this.port = nextPort++ + this.v = 0 + this.configdb = map (function(s) {return s.host()}, configSet.configSvrs) .join(',') + this.chunkSize = 1 +} + +// Start router (mongos) with this spec and return connection to it +// Router -> IO Connection +Router.prototype.begin = function() { + return startMongos (this) +} + +// Stop router +// Router -> IO () +Router.prototype.end = function() { + return stopMongoProgram (this.port) +} + +// Add shard to config via router (mongos) connection. Shard is either a replSet name (replSet.getURL()) or single server (server.host) +// Connection -> String -> IO () +function addShard (routerConn, repSetOrHostName) { + var ack = routerConn.getDB('admin').runCommand ({addshard: repSetOrHostName}) + assert (ack['ok'], tojson(ack)) +} + +// Connection -> String -> IO () +function enableSharding (routerConn, dbName) { + var ack = routerConn.getDB('admin').runCommand ({enablesharding: dbName}) + assert (ack['ok'], tojson(ack)) +} + +// Connection -> String -> String -> String -> IO () +function shardCollection (routerConn, dbName, collName, shardKey) { + var ack = routerConn.getDB('admin').runCommand ({shardcollection: dbName + '.' + collName, key: shardKey}) + assert (ack['ok'], tojson(ack)) +} + +// Move db from its current primary shard to given shard. Shard is either a replSet name (replSet.getURL()) or single server (server.host) +// Connection -> String -> String -> IO () +function moveDB (routerConn, dbname, repSetOrHostName) { + var ack = routerConn.getDB('admin').runCommand ({moveprimary: dbname, to: repSetOrHostName}) + printjson(ack) + assert (ack['ok'], tojson(ack)) +} diff --git a/jstests/libs/network.js b/jstests/libs/network.js new file mode 100644 index 0000000..e5b33f3 --- /dev/null +++ b/jstests/libs/network.js @@ -0,0 +1,37 @@ + +// Parse "127.0.0.1:300" into {addr: "127.0.0.1", port: 300}, +// and "127.0.0.1" into {addr: "127.0.0.1", port: undefined} +function parseHost (hostString) { + var items = hostString.match(/(\d+.\d+.\d+.\d+)(:(\d+))?/) + return {addr: items[1], port: parseInt(items[3])} +} + + +/* Network traffic shaping (packet dropping) to simulate network problems + Currently works on BSD Unix and Mac OS X only (using ipfw). + Requires sudo access. + TODO: make it work on Linux too (using iptables). */ + +var nextRuleNum = 100 // this grows indefinitely but can't exceed 65534, so can't call routines below indefinitely +var portRuleNum = {} + +// Cut network connection to local port by dropping packets using iptables +function cutNetwork (port) { + portRuleNum[port] = nextRuleNum + runProgram ('sudo', 'ipfw', 'add ' + nextRuleNum++ + ' deny tcp from any to any ' + port) + runProgram ('sudo', 'ipfw', 'add ' + nextRuleNum++ + ' deny tcp from any ' + port + ' to any') + //TODO: confirm it worked (since sudo may not work) + runProgram ('sudo', 'ipfw', 'show') +} + +// Restore network connection to local port by not dropping packets using iptables +function restoreNetwork (port) { + var ruleNum = portRuleNum[port] + if (ruleNum) { + runProgram ('sudo', 'ipfw', 'delete ' + ruleNum++) + runProgram ('sudo', 'ipfw', 'delete ' + ruleNum) + delete portRuleNum[port] + } + //TODO: confirm it worked (since sudo may not work) + runProgram ('sudo', 'ipfw', 'show') +} |