summaryrefslogtreecommitdiff
path: root/buildscripts
diff options
context:
space:
mode:
authorAntonin Kral <a.kral@bobek.cz>2012-08-29 20:54:51 +0200
committerAntonin Kral <a.kral@bobek.cz>2012-08-29 20:54:51 +0200
commit83957b73f9177f6e38bd5375bd93ca1f6a47188c (patch)
treef20b7d6ac9a9c64ff5bb6b5910a24abbb356b1d5 /buildscripts
parent5071d203970edd4c995493d810abe20987e76fe9 (diff)
Imported Upstream version 2.2.0upstream/2.2.0
Diffstat (limited to 'buildscripts')
-rw-r--r--buildscripts/__init__.py8
-rw-r--r--buildscripts/bb.py2
-rw-r--r--buildscripts/bcp.py40
-rw-r--r--buildscripts/benchmark_tools.py62
-rwxr-xr-xbuildscripts/build_and_test_client.py75
-rw-r--r--buildscripts/buildlogger.py480
-rw-r--r--buildscripts/cleanbb.py15
-rw-r--r--buildscripts/confluence_export.py103
-rw-r--r--buildscripts/distmirror.py139
-rw-r--r--buildscripts/emr/FileLock.java107
-rw-r--r--buildscripts/emr/IOUtil.java156
-rw-r--r--buildscripts/emr/MANIFEST.MF2
-rw-r--r--buildscripts/emr/emr.java380
-rw-r--r--buildscripts/emr/emr.py385
-rw-r--r--buildscripts/emr/emrnodesetup.sh7
-rwxr-xr-xbuildscripts/errorcodes.py76
-rw-r--r--buildscripts/hacks_mandriva.py9
-rw-r--r--buildscripts/hacks_ubuntu.py54
-rwxr-xr-xbuildscripts/make_archive.py116
-rw-r--r--buildscripts/moduleconfig.py131
-rw-r--r--buildscripts/packager.py37
-rw-r--r--buildscripts/s3md5.py4
-rwxr-xr-xbuildscripts/smoke.py512
-rw-r--r--buildscripts/utils.py83
24 files changed, 2468 insertions, 515 deletions
diff --git a/buildscripts/__init__.py b/buildscripts/__init__.py
index 839da9dd919..442c233b031 100644
--- a/buildscripts/__init__.py
+++ b/buildscripts/__init__.py
@@ -1,13 +1,5 @@
-import hacks_mandriva
-import hacks_ubuntu
import os;
def findHacks( un ):
- if un[0] == 'Linux' and (os.path.exists("/etc/debian_version") or
- un[3].find("Ubuntu") >= 0):
- return hacks_ubuntu
- if un[0] == 'Linux' and (os.path.exists("/etc/mandriva-release") or
- un[3].find("mnb") >= 0):
- return hacks_mandriva
return None
diff --git a/buildscripts/bb.py b/buildscripts/bb.py
index 1e878283fed..d2f3819b1de 100644
--- a/buildscripts/bb.py
+++ b/buildscripts/bb.py
@@ -13,7 +13,7 @@ def checkOk():
m = "v" + m[0]
print( m )
- print( "excpted version [" + m + "]" )
+ print( "expected version [" + m + "]" )
from subprocess import Popen, PIPE
diff = Popen( [ "git", "diff", "origin/v1.2" ], stdout=PIPE ).communicate()[ 0 ]
diff --git a/buildscripts/bcp.py b/buildscripts/bcp.py
new file mode 100644
index 00000000000..dda20924e79
--- /dev/null
+++ b/buildscripts/bcp.py
@@ -0,0 +1,40 @@
+
+import utils
+import os
+import shutil
+import sys
+
+def go( boost_root ):
+
+ OUTPUT = "src/third_party/boost"
+ if os.path.exists( OUTPUT ):
+ shutil.rmtree( OUTPUT )
+
+ cmd = [ "bcp" , "--scan" , "--boost=%s" % boost_root ]
+
+ src = utils.getAllSourceFiles()
+
+ cmd += src
+ cmd.append( OUTPUT )
+
+ if not os.path.exists( OUTPUT ):
+ os.makedirs( OUTPUT )
+
+ res = utils.execsys( cmd )
+
+ out = open( OUTPUT + "/bcp-out.txt" , 'w' )
+ out.write( res[0] )
+ out.close()
+
+ out = open( OUTPUT + "/notes.txt" , 'w' )
+ out.write( "command: " + " ".join( cmd ) )
+ out.close()
+
+ print( res[1] )
+
+if __name__ == "__main__":
+ if len(sys.argv) == 1:
+ print( "usage: python %s <boost root directory>" % sys.argv[0] )
+ sys.exit(1)
+ go( sys.argv[1] )
+
diff --git a/buildscripts/benchmark_tools.py b/buildscripts/benchmark_tools.py
deleted file mode 100644
index 54a86b019d5..00000000000
--- a/buildscripts/benchmark_tools.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import os
-import urllib
-import urllib2
-import sys
-
-try:
- import json
-except:
- import simplejson as json # need simplejson for python < 2.6
-
-sys.path.append( "." )
-sys.path.append( ".." )
-sys.path.append( "../../" )
-sys.path.append( "../../../" )
-
-
-import settings
-
-def machine_info(extra_info=""):
- """Get a dict representing the "machine" section of a benchmark result.
-
- ie:
- {
- "os_name": "OS X",
- "os_version": "10.5",
- "processor": "2.4 GHz Intel Core 2 Duo",
- "memory": "3 GB 667 MHz DDR2 SDRAM",
- "extra_info": "Python 2.6"
- }
-
- Must have a settings.py file on sys.path that defines "processor" and "memory"
- variables.
- """
- machine = {}
- (machine["os_name"], _, machine["os_version"], _, _) = os.uname()
- machine["processor"] = settings.processor
- machine["memory"] = settings.memory
- machine["extra_info"] = extra_info
- return machine
-
-def post_data(data, machine_extra_info="", post_url="http://mongo-db.appspot.com/benchmark"):
- """Post a benchmark data point.
-
- data should be a Python dict that looks like:
- {
- "benchmark": {
- "project": "http://github.com/mongodb/mongo-python-driver",
- "name": "insert test",
- "description": "test inserting 10000 documents with the C extension enabled",
- "tags": ["insert", "python"]
- },
- "trial": {
- "server_hash": "4f5a8d52f47507a70b6c625dfb5dbfc87ba5656a",
- "client_hash": "8bf2ad3d397cbde745fd92ad41c5b13976fac2b5",
- "result": 67.5,
- "extra_info": "some logs or something"
- }
- }
- """
- data["machine"] = machine_info(machine_extra_info)
- urllib2.urlopen(post_url, urllib.urlencode({"payload": json.dumps(data)}))
- return data
diff --git a/buildscripts/build_and_test_client.py b/buildscripts/build_and_test_client.py
new file mode 100755
index 00000000000..1b97f623cc5
--- /dev/null
+++ b/buildscripts/build_and_test_client.py
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+
+'''Script to attempt an isolated build of the C++ driver and its examples.
+
+Working directory must be the repository root.
+
+Usage:
+
+./buildscripts/build_and_test_client.py <mongo client archive file> [optional scons arguments]
+
+The client is built in a temporary directory, and the sample programs are run against a mongod
+instance found in the current working directory. The temporary directory and its contents are
+destroyed at the end of execution.
+'''
+
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+import tarfile
+import zipfile
+
+import utils
+
+def main(args):
+ archive_file = args[1]
+ scons_args = args[2:]
+ build_and_test(archive_file, scons_args)
+
+def build_and_test(archive_name, scons_args):
+ work_dir = tempfile.mkdtemp()
+ try:
+ archive = open_archive(archive_name)
+ extracted_root = extract_archive(work_dir, archive)
+ run_scons(extracted_root, scons_args)
+ smoke_client(extracted_root)
+ finally:
+ shutil.rmtree(work_dir)
+
+def open_tar(archive_name):
+ return tarfile.open(archive_name, 'r')
+
+def open_zip(archive_name):
+ class ZipWrapper(zipfile.ZipFile):
+ def getnames(self):
+ return self.namelist()
+ return ZipWrapper(archive_name, 'r')
+
+def open_archive(archive_name):
+ try:
+ return open_tar(archive_name)
+ except:
+ return open_zip(archive_name)
+
+def extract_archive(work_dir, archive_file):
+ archive_file.extractall(path=work_dir)
+ return os.path.join(
+ work_dir,
+ os.path.dirname([n for n in archive_file.getnames() if n.endswith('SConstruct')][0])
+ )
+
+def run_scons(extracted_root, scons_args):
+ rc = subprocess.call(['scons', '-C', extracted_root, ] + scons_args + ['clientTests'])
+ if rc is not 0:
+ sys.exit(rc)
+
+def smoke_client(extracted_root):
+ rc = subprocess.call(utils.smoke_command("--test-path", extracted_root, "client"))
+ if rc is not 0:
+ sys.exit(rc)
+
+if __name__ == '__main__':
+ main(sys.argv)
+ sys.exit(0)
diff --git a/buildscripts/buildlogger.py b/buildscripts/buildlogger.py
new file mode 100644
index 00000000000..9f3feacbe55
--- /dev/null
+++ b/buildscripts/buildlogger.py
@@ -0,0 +1,480 @@
+"""
+buildlogger.py
+
+Wrap a command (specified on the command line invocation of buildlogger.py)
+and send output in batches to the buildlogs web application via HTTP POST.
+
+The script configures itself from environment variables:
+
+ required env vars:
+ MONGO_BUILDER_NAME (e.g. "Nightly Linux 64-bit")
+ MONGO_BUILD_NUMBER (an integer)
+ MONGO_TEST_FILENAME (not required when invoked with -g)
+
+ optional env vars:
+ MONGO_PHASE (e.g. "core", "slow nightly", etc)
+ MONGO_* (any other environment vars are passed to the web app)
+ BUILDLOGGER_CREDENTIALS (see below)
+
+This script has two modes: a "test" mode, intended to wrap the invocation of
+an individual test file, and a "global" mode, intended to wrap the mongod
+instances that run throughout the duration of a mongo test phase (the logs
+from "global" invocations are displayed interspersed with the logs of each
+test, in order to let the buildlogs web app display the full output sensibly.)
+
+If the BUILDLOGGER_CREDENTIALS environment variable is set, it should be a
+path to a valid Python file containing "username" and "password" variables,
+which should be valid credentials for authenticating to the buildlogger web
+app. For example:
+
+ username = "hello"
+ password = "world"
+
+If BUILDLOGGER_CREDENTIALS is a relative path, then the working directory
+and the directories one, two, and three levels up, are searched, in that
+order.
+"""
+
+import functools
+import os
+import os.path
+import re
+import signal
+import socket
+import subprocess
+import sys
+import time
+import traceback
+import urllib2
+import utils
+
+# suppress deprecation warnings that happen when
+# we import the 'buildbot.tac' file below
+import warnings
+warnings.simplefilter('ignore', DeprecationWarning)
+
+try:
+ import json
+except:
+ try:
+ import simplejson as json
+ except:
+ json = None
+
+# try to load the shared secret from settings.py
+# which will be one, two, or three directories up
+# from this file's location
+credentials_file = os.environ.get('BUILDLOGGER_CREDENTIALS', 'buildbot.tac')
+credentials_loc, credentials_name = os.path.split(credentials_file)
+if not credentials_loc:
+ here = os.path.abspath(os.path.dirname(__file__))
+ possible_paths = [
+ os.path.abspath(os.path.join(here, '..')),
+ os.path.abspath(os.path.join(here, '..', '..')),
+ os.path.abspath(os.path.join(here, '..', '..', '..')),
+ ]
+else:
+ possible_paths = [credentials_loc]
+
+username, password = None, None
+for path in possible_paths:
+ credentials_path = os.path.join(path, credentials_name)
+ if os.path.isfile(credentials_path):
+ credentials = {}
+ try:
+ execfile(credentials_path, credentials, credentials)
+ username = credentials.get('slavename', credentials.get('username'))
+ password = credentials.get('passwd', credentials.get('password'))
+ break
+ except:
+ pass
+
+
+URL_ROOT = 'http://buildlogs.mongodb.org/'
+TIMEOUT_SECONDS = 10
+socket.setdefaulttimeout(TIMEOUT_SECONDS)
+
+digest_handler = urllib2.HTTPDigestAuthHandler()
+digest_handler.add_password(
+ realm='buildlogs',
+ uri=URL_ROOT,
+ user=username,
+ passwd=password)
+
+# This version of HTTPErrorProcessor is copied from
+# Python 2.7, and allows REST response codes (e.g.
+# "201 Created") which are treated as errors by
+# older versions.
+class HTTPErrorProcessor(urllib2.HTTPErrorProcessor):
+ def http_response(self, request, response):
+ code, msg, hdrs = response.code, response.msg, response.info()
+
+ # According to RFC 2616, "2xx" code indicates that the client's
+ # request was successfully received, understood, and accepted.
+ if not (200 <= code < 300):
+ response = self.parent.error(
+ 'http', request, response, code, msg, hdrs)
+
+ return response
+
+url_opener = urllib2.build_opener(digest_handler, HTTPErrorProcessor())
+
+def url(endpoint):
+ if not endpoint.endswith('/'):
+ endpoint = '%s/' % endpoint
+
+ return '%s/%s' % (URL_ROOT.rstrip('/'), endpoint)
+
+def post(endpoint, data, headers=None):
+ data = json.dumps(data, encoding='utf-8')
+
+ headers = headers or {}
+ headers.update({'Content-Type': 'application/json; charset=utf-8'})
+
+ req = urllib2.Request(url=url(endpoint), data=data, headers=headers)
+ try:
+ response = url_opener.open(req)
+ except urllib2.URLError:
+ import traceback
+ traceback.print_exc(file=sys.stderr)
+ sys.stderr.flush()
+ # indicate that the request did not succeed
+ return None
+
+ response_headers = dict(response.info())
+
+ # eg "Content-Type: application/json; charset=utf-8"
+ content_type = response_headers.get('content-type')
+ match = re.match(r'(?P<mimetype>[^;]+).*(?:charset=(?P<charset>[^ ]+))?$', content_type)
+ if match and match.group('mimetype') == 'application/json':
+ encoding = match.group('charset') or 'utf-8'
+ return json.load(response, encoding=encoding)
+
+ return response.read()
+
+def traceback_to_stderr(func):
+ """
+ decorator which logs any exceptions encountered to stderr
+ and returns none.
+ """
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except urllib2.HTTPError, err:
+ sys.stderr.write('error: HTTP code %d\n----\n' % err.code)
+ if hasattr(err, 'hdrs'):
+ for k, v in err.hdrs.items():
+ sys.stderr.write("%s: %s\n" % (k, v))
+ sys.stderr.write('\n')
+ sys.stderr.write(err.read())
+ sys.stderr.write('\n----\n')
+ sys.stderr.flush()
+ except:
+ sys.stderr.write('Traceback from buildlogger:\n')
+ traceback.print_exc(file=sys.stderr)
+ sys.stderr.flush()
+ return None
+ return wrapper
+
+
+@traceback_to_stderr
+def get_or_create_build(builder, buildnum, extra={}):
+ data = {'builder': builder, 'buildnum': buildnum}
+ data.update(extra)
+ response = post('build', data)
+ if response is None:
+ return None
+ return response['id']
+
+@traceback_to_stderr
+def create_test(build_id, test_filename, test_command, test_phase):
+ response = post('build/%s/test' % build_id, {
+ 'test_filename': test_filename,
+ 'command': test_command,
+ 'phase': test_phase,
+ })
+ if response is None:
+ return None
+ return response['id']
+
+@traceback_to_stderr
+def append_test_logs(build_id, test_id, log_lines):
+ response = post('build/%s/test/%s' % (build_id, test_id), data=log_lines)
+ if response is None:
+ return False
+ return True
+
+@traceback_to_stderr
+def append_global_logs(build_id, log_lines):
+ """
+ "global" logs are for the mongod(s) started by smoke.py
+ that last the duration of a test phase -- since there
+ may be output in here that is important but spans individual
+ tests, the buildlogs webapp handles these logs specially.
+ """
+ response = post('build/%s' % build_id, data=log_lines)
+ if response is None:
+ return False
+ return True
+
+@traceback_to_stderr
+def finish_test(build_id, test_id, failed=False):
+ response = post('build/%s/test/%s' % (build_id, test_id), data=[], headers={
+ 'X-Sendlogs-Test-Done': 'true',
+ 'X-Sendlogs-Test-Failed': failed and 'true' or 'false',
+ })
+ if response is None:
+ return False
+ return True
+
+def run_and_echo(command):
+ """
+ this just calls the command, and returns its return code,
+ allowing stdout and stderr to work as normal. it is used
+ as a fallback when environment variables or python
+ dependencies cannot be configured, or when the logging
+ webapp is unavailable, etc
+ """
+ proc = subprocess.Popen(command)
+
+ def handle_sigterm(signum, frame):
+ try:
+ proc.send_signal(signum)
+ except AttributeError:
+ os.kill(proc.pid, signum)
+ orig_handler = signal.signal(signal.SIGTERM, handle_sigterm)
+
+ proc.wait()
+
+ signal.signal(signal.SIGTERM, orig_handler)
+ return proc.returncode
+
+class LogAppender(object):
+ def __init__(self, callback, args, send_after_lines=200, send_after_seconds=2):
+ self.callback = callback
+ self.callback_args = args
+
+ self.send_after_lines = send_after_lines
+ self.send_after_seconds = send_after_seconds
+
+ self.buf = []
+ self.retrybuf = []
+ self.last_sent = time.time()
+
+ def __call__(self, line):
+ self.buf.append((time.time(), line))
+
+ delay = time.time() - self.last_sent
+ if len(self.buf) >= self.send_after_lines or delay >= self.send_after_seconds:
+ self.submit()
+
+ # no return value is expected
+
+ def submit(self):
+ if len(self.buf) + len(self.retrybuf) == 0:
+ return True
+
+ args = list(self.callback_args)
+ args.append(list(self.buf) + self.retrybuf)
+
+ self.last_sent = time.time()
+
+ if self.callback(*args):
+ self.buf = []
+ self.retrybuf = []
+ return True
+ else:
+ self.retrybuf += self.buf
+ self.buf = []
+ return False
+
+
+def wrap_test(command):
+ """
+ call the given command, intercept its stdout and stderr,
+ and send results in batches of 100 lines or 10s to the
+ buildlogger webapp
+ """
+
+ # get builder name and build number from environment
+ builder = os.environ.get('MONGO_BUILDER_NAME')
+ buildnum = os.environ.get('MONGO_BUILD_NUMBER')
+
+ if builder is None or buildnum is None:
+ return run_and_echo(command)
+
+ try:
+ buildnum = int(buildnum)
+ except ValueError:
+ sys.stderr.write('buildlogger: build number ("%s") was not an int\n' % buildnum)
+ sys.stderr.flush()
+ return run_and_echo(command)
+
+ # test takes some extra info
+ phase = os.environ.get('MONGO_PHASE', 'unknown')
+ test_filename = os.environ.get('MONGO_TEST_FILENAME', 'unknown')
+
+ build_info = dict((k, v) for k, v in os.environ.items() if k.startswith('MONGO_'))
+ build_info.pop('MONGO_BUILDER_NAME', None)
+ build_info.pop('MONGO_BUILD_NUMBER', None)
+ build_info.pop('MONGO_PHASE', None)
+ build_info.pop('MONGO_TEST_FILENAME', None)
+
+ build_id = get_or_create_build(builder, buildnum, extra=build_info)
+ if not build_id:
+ return run_and_echo(command)
+
+ test_id = create_test(build_id, test_filename, ' '.join(command), phase)
+ if not test_id:
+ return run_and_echo(command)
+
+ # the peculiar formatting here matches what is printed by
+ # smoke.py when starting tests
+ output_url = '%s/build/%s/test/%s/' % (URL_ROOT.rstrip('/'), build_id, test_id)
+ sys.stdout.write(' (output suppressed; see %s)\n' % output_url)
+ sys.stdout.flush()
+
+ callback = LogAppender(callback=append_test_logs, args=(build_id, test_id))
+ returncode = loop_and_callback(command, callback)
+ failed = bool(returncode != 0)
+
+ # this will append any remaining unsubmitted logs, or
+ # return True if there are none left to submit
+ tries = 5
+ while not callback.submit() and tries > 0:
+ sys.stderr.write('failed to finish sending test logs, retrying in 1s\n')
+ sys.stderr.flush()
+ time.sleep(1)
+ tries -= 1
+
+ tries = 5
+ while not finish_test(build_id, test_id, failed) and tries > 5:
+ sys.stderr.write('failed to mark test finished, retrying in 1s\n')
+ sys.stderr.flush()
+ time.sleep(1)
+ tries -= 1
+
+ return returncode
+
+def wrap_global(command):
+ """
+ call the given command, intercept its stdout and stderr,
+ and send results in batches of 100 lines or 10s to the
+ buildlogger webapp. see :func:`append_global_logs` for the
+ difference between "global" and "test" log output.
+ """
+
+ # get builder name and build number from environment
+ builder = os.environ.get('MONGO_BUILDER_NAME')
+ buildnum = os.environ.get('MONGO_BUILD_NUMBER')
+
+ if builder is None or buildnum is None:
+ return run_and_echo(command)
+
+ try:
+ buildnum = int(buildnum)
+ except ValueError:
+ sys.stderr.write('int(os.environ["MONGO_BUILD_NUMBER"]):\n')
+ sys.stderr.write(traceback.format_exc())
+ sys.stderr.flush()
+ return run_and_echo(command)
+
+ build_info = dict((k, v) for k, v in os.environ.items() if k.startswith('MONGO_'))
+ build_info.pop('MONGO_BUILDER_NAME', None)
+ build_info.pop('MONGO_BUILD_NUMBER', None)
+
+ build_id = get_or_create_build(builder, buildnum, extra=build_info)
+ if not build_id:
+ return run_and_echo(command)
+
+ callback = LogAppender(callback=append_global_logs, args=(build_id, ))
+ returncode = loop_and_callback(command, callback)
+
+ # this will append any remaining unsubmitted logs, or
+ # return True if there are none left to submit
+ tries = 5
+ while not callback.submit() and tries > 0:
+ sys.stderr.write('failed to finish sending global logs, retrying in 1s\n')
+ sys.stderr.flush()
+ time.sleep(1)
+ tries -= 1
+
+ return returncode
+
+def loop_and_callback(command, callback):
+ """
+ run the given command (a sequence of arguments, ordinarily
+ from sys.argv), and call the given callback with each line
+ of stdout or stderr encountered. after the command is finished,
+ callback is called once more with None instead of a string.
+ """
+ proc = subprocess.Popen(
+ command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
+
+ def handle_sigterm(signum, frame):
+ try:
+ proc.send_signal(signum)
+ except AttributeError:
+ os.kill(proc.pid, signum)
+
+ # register a handler to delegate SIGTERM
+ # to the child process
+ orig_handler = signal.signal(signal.SIGTERM, handle_sigterm)
+
+ while proc.poll() is None:
+ try:
+ line = proc.stdout.readline().strip('\r\n')
+ line = utils.unicode_dammit(line)
+ callback(line)
+ except IOError:
+ # if the signal handler is called while
+ # we're waiting for readline() to return,
+ # don't show a traceback
+ break
+
+ # There may be additional buffered output
+ for line in proc.stdout.readlines():
+ callback(line.strip('\r\n'))
+
+ # restore the original signal handler, if any
+ signal.signal(signal.SIGTERM, orig_handler)
+ return proc.returncode
+
+
+if __name__ == '__main__':
+ # argv[0] is 'buildlogger.py'
+ del sys.argv[0]
+
+ if sys.argv[0] in ('-g', '--global'):
+ # then this is wrapping a "global" command, and should
+ # submit global logs to the build, not test logs to a
+ # test within the build
+ del sys.argv[0]
+ wrapper = wrap_global
+
+ else:
+ wrapper = wrap_test
+
+ # if we are missing credentials or the json module, then
+ # we can't use buildlogger; so just echo output, but also
+ # log why we can't work.
+ if json is None:
+ sys.stderr.write('buildlogger: could not import a json module\n')
+ sys.stderr.flush()
+ wrapper = run_and_echo
+
+ elif username is None or password is None:
+ sys.stderr.write('buildlogger: could not find or import %s for authentication\n' % credentials_file)
+ sys.stderr.flush()
+ wrapper = run_and_echo
+
+ # otherwise wrap a test command as normal; the
+ # wrapper functions return the return code of
+ # the wrapped command, so that should be our
+ # exit code as well.
+ sys.exit(wrapper(sys.argv))
+
diff --git a/buildscripts/cleanbb.py b/buildscripts/cleanbb.py
index bfeafd2d8e9..085eee223fb 100644
--- a/buildscripts/cleanbb.py
+++ b/buildscripts/cleanbb.py
@@ -14,6 +14,16 @@ if os.path.basename(cwd) == 'buildscripts':
print( "cwd [" + cwd + "]" )
def shouldKill( c ):
+
+ if "smoke.py" in c:
+ return False
+
+ if "emr.py" in c:
+ return False
+
+ if "java" in c:
+ return False
+
if c.find( cwd ) >= 0:
return True
@@ -25,7 +35,10 @@ def shouldKill( c ):
def killprocs( signal="" ):
killed = 0
-
+
+ if sys.platform == 'win32':
+ return killed
+
l = utils.getprocesslist()
print( "num procs:" + str( len( l ) ) )
if len(l) == 0:
diff --git a/buildscripts/confluence_export.py b/buildscripts/confluence_export.py
deleted file mode 100644
index 29cdde60c7f..00000000000
--- a/buildscripts/confluence_export.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#! /usr/bin/env python
-
-# Export the contents on confluence
-#
-# Dependencies:
-# - suds
-#
-# User: soap, Password: soap
-from __future__ import with_statement
-import cookielib
-import datetime
-import os
-import shutil
-import subprocess
-import sys
-import urllib2
-sys.path[0:0] = [""]
-
-import simples3
-from suds.client import Client
-
-import settings
-
-HTML_URI = "http://mongodb.onconfluence.com/rpc/soap-axis/confluenceservice-v1?wsdl"
-PDF_URI = "http://www.mongodb.org/rpc/soap-axis/pdfexport?wsdl"
-USERNAME = "soap"
-PASSWORD = "soap"
-AUTH_URI = "http://www.mongodb.org/login.action?os_authType=basic"
-TMP_DIR = "confluence-tmp"
-TMP_FILE = "confluence-tmp.zip"
-
-
-def export_html_and_get_uri():
- client = Client(HTML_URI)
- auth = client.service.login(USERNAME, PASSWORD)
- return client.service.exportSpace(auth, "DOCS", "TYPE_HTML")
-
-
-def export_pdf_and_get_uri():
- client = Client(PDF_URI)
- auth = client.service.login(USERNAME, PASSWORD)
- return client.service.exportSpace(auth, "DOCS")
-
-
-def login_and_download(docs):
- cookie_jar = cookielib.CookieJar()
- cookie_handler = urllib2.HTTPCookieProcessor(cookie_jar)
- password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
- password_manager.add_password(None, AUTH_URI, USERNAME, PASSWORD)
- auth_handler = urllib2.HTTPBasicAuthHandler(password_manager)
- urllib2.build_opener(cookie_handler, auth_handler).open(AUTH_URI)
- return urllib2.build_opener(cookie_handler).open(docs)
-
-
-def extract_to_dir(data, dir):
- with open(TMP_FILE, "w") as f:
- f.write(data.read())
- data.close()
- # This is all really annoying but zipfile doesn't do extraction on 2.5
- subprocess.call(["unzip", "-d", dir, TMP_FILE])
- os.unlink(TMP_FILE)
-
-
-def rmdir(dir):
- try:
- shutil.rmtree(dir)
- except:
- pass
-
-
-def overwrite(src, dest):
- target = "%s/DOCS-%s/" % (dest, datetime.date.today())
- current = "%s/current" % dest
- rmdir(target)
- shutil.copytree(src, target)
- try:
- os.unlink(current)
- except:
- pass
- os.symlink(os.path.abspath(target), os.path.abspath(current))
-
-
-def write_to_s3(pdf):
- s3 = simples3.S3Bucket(settings.bucket, settings.id, settings.key)
- name = "docs/mongodb-docs-%s.pdf" % datetime.date.today()
- s3.put(name, pdf, acl="public-read")
-
-
-def main(dir):
- # HTML
- rmdir(TMP_DIR)
- extract_to_dir(login_and_download(export_html_and_get_uri()), TMP_DIR)
- overwrite("%s/DOCS/" % TMP_DIR, dir)
-
- # PDF
- write_to_s3(login_and_download(export_pdf_and_get_uri()).read())
-
-
-if __name__ == "__main__":
- try:
- main(sys.argv[1])
- except IndexError:
- print "pass outdir as first arg"
diff --git a/buildscripts/distmirror.py b/buildscripts/distmirror.py
deleted file mode 100644
index 7af1a89f7dc..00000000000
--- a/buildscripts/distmirror.py
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/usr/bin/env python
-
-# Download mongodb stuff (at present builds, sources, docs, but not
-# drivers).
-
-# Usage: <progname> [directory] # directory defaults to cwd.
-
-# FIXME: this script is fairly sloppy.
-import sys
-import os
-import urllib2
-import time
-import hashlib
-import warnings
-
-written_files = []
-def get(url, filename):
- # A little safety check.
- if filename in written_files:
- raise Exception('not overwriting file %s (already written in this session)' % filename)
- else:
- written_files.append(filename)
- print "downloading %s to %s" % (url, filename)
- open(filename, 'w').write(urllib2.urlopen(url).read())
-
-
-def checkmd5(md5str, filename):
- m = hashlib.md5()
- m.update(open(filename, 'rb').read())
- d = m.hexdigest()
- if d != md5str:
- warnings.warn("md5sum mismatch for file %s: wanted %s; got %s" % (filename, md5str, d))
-
-osarches=(("osx", ("i386", "i386-tiger", "x86_64"), ("tgz", )),
- ("linux", ("i686", "x86_64"), ("tgz", )),
- ("win32", ("i386", "x86_64"), ("zip", )),
- ("sunos5", ("i86pc", "x86_64"), ("tgz", )),
- ("src", ("src", ), ("tar.gz", "zip")), )
-
-# KLUDGE: this will need constant editing.
-versions = ("1.4.2", "1.5.1", "latest")
-
-url_format = "http://downloads.mongodb.org/%s/mongodb-%s-%s.%s"
-filename_format = "mongodb-%s-%s.%s"
-
-def core_server():
- for version in versions:
- for (os, architectures, archives) in osarches:
- for architecture in architectures:
- for archive in archives:
- osarch = os + '-' + architecture if architecture != 'src' else 'src'
- # ugh.
- if architecture == 'src' and version == 'latest':
- if archive == 'tar.gz':
- archive2 = 'tarball'
- elif archive == 'zip':
- archive2 == 'zipball'
- url = "http://github.com/mongodb/mongo/"+archive2+"/master"
- version2 = "master"
- else:
- version2 = version if architecture != 'src' else 'r'+version
- url = url_format % (os, osarch, version2, archive)
- # ugh ugh
- md5url = url+'.md5' if architecture != 'src' else None
- filename = filename_format % (osarch, version2, archive)
- get(url, filename)
- if md5url:
- print "fetching md5 url " + md5url
- md5str = urllib2.urlopen(md5url).read()
- checkmd5(md5str, filename)
-
-def drivers():
- # Drivers... FIXME: drivers.
- driver_url_format = "http://github.com/mongodb/mongo-%s-driver/%s/%s"
- driver_filename_format = "mongo-%s-driver-%s.%s"
- drivers=(("python", ("1.6", "master"), ("zipball", "tarball"), None),
- ("ruby", ("0.20", "master"), ("zipball", "tarball"), None),
- ("c", ("v0.1", "master"), ("zipball", "tarball"), None),
- # FIXME: PHP, Java, and Csharp also have zips and jars of
- # precompiled relesaes.
- ("php", ("1.0.6", "master"), ("zipball", "tarball"), None),
- ("java", ("r1.4", "r2.0rc1", "master"), ("zipball", "tarball"), None),
- # And Csharp is in a different github place, too.
- ("csharp", ("0.82.2", "master"), ("zipball", "tarball"),
- "http://github.com/samus/mongodb-%s/%s/%s"),
- )
-
- for (lang, releases, archives, url_format) in drivers:
- for release in releases:
- for archive in archives:
- url = (url_format if url_format else driver_url_format) % (lang, archive, release)
- if archive == 'zipball':
- extension = 'zip'
- elif archive == 'tarball':
- extension = 'tgz'
- else:
- raise Exception('unknown archive format %s' % archive)
- filename = driver_filename_format % (lang, release, extension)
- get(url, filename)
- # ugh ugh ugh
- if lang == 'csharp' and release != 'master':
- url = 'http://github.com/downloads/samus/mongodb-csharp/MongoDBDriver-Release-%.zip' % (release)
- filename = 'MongoDBDriver-Release-%.zip' % (release)
- get(url, filename)
- if lang == 'java' and release != 'master':
- get('http://github.com/downloads/mongodb/mongo-java-driver/mongo-%s.jar' % (release), 'mongo-%s.jar' % (release))
- # I have no idea what's going on with the PHP zipfiles.
- if lang == 'php' and release == '1.0.6':
- get('http://github.com/downloads/mongodb/mongo-php-driver/mongo-1.0.6-php5.2-osx.zip', 'mongo-1.0.6-php5.2-osx.zip')
- get('http://github.com/downloads/mongodb/mongo-php-driver/mongo-1.0.6-php5.3-osx.zip', 'mongo-1.0.6-php5.3-osx.zip')
-
-def docs():
- # FIXME: in principle, the doc PDFs could be out of date.
- docs_url = time.strftime("http://downloads.mongodb.org/docs/mongodb-docs-%Y-%m-%d.pdf")
- docs_filename = time.strftime("mongodb-docs-%Y-%m-%d.pdf")
- get(docs_url, docs_filename)
-
-def extras():
- # Extras
- extras = ("http://media.mongodb.org/zips.json", )
- for extra in extras:
- if extra.rfind('/') > -1:
- filename = extra[extra.rfind('/')+1:]
- else:
- raise Exception('URL %s lacks a slash?' % extra)
- get(extra, filename)
-
-if len(sys.argv) > 1:
- dir=sys.argv[1]
- os.makedirs(dir)
- os.chdir(dir)
-
-print """NOTE: the md5sums for all the -latest tarballs are out of
-date. You will probably see warnings as this script runs. (If you
-don't, feel free to delete this note.)"""
-core_server()
-drivers()
-docs()
-extras()
diff --git a/buildscripts/emr/FileLock.java b/buildscripts/emr/FileLock.java
new file mode 100644
index 00000000000..52ed4c083b4
--- /dev/null
+++ b/buildscripts/emr/FileLock.java
@@ -0,0 +1,107 @@
+// FileLock.java
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * "locks" a resource by using the file system as storage
+ * file has 1 line
+ * <incarnation> <last ping time in millis>
+ */
+public class FileLock {
+
+ public FileLock( String logicalName )
+ throws IOException {
+
+ _file = new File( "/tmp/java-fileLock-" + logicalName );
+ _incarnation = "xxx" + Math.random() + "yyy";
+
+ if ( ! _file.exists() ) {
+ FileOutputStream fout = new FileOutputStream( _file );
+ fout.write( "\n".getBytes() );
+ fout.close();
+ }
+
+ }
+
+ /**
+ * takes lock
+ * if someone else has it, blocks until the other one finishes
+ */
+ public void lock()
+ throws IOException {
+ if ( _lock != null )
+ throw new IllegalStateException( "can't lock when you're locked" );
+
+ try {
+ _semaphore.acquire();
+ }
+ catch ( InterruptedException ie ) {
+ throw new RuntimeException( "sad" , ie );
+ }
+
+ _raf = new RandomAccessFile( _file , "rw" );
+ _lock = _raf.getChannel().lock();
+ }
+
+ public void unlock()
+ throws IOException {
+
+ if ( _lock == null )
+ throw new IllegalStateException( "can't unlock when you're not locked" );
+
+ _lock.release();
+ _semaphore.release();
+
+ _locked = false;
+ }
+
+ final File _file;
+ final String _incarnation;
+
+ private RandomAccessFile _raf;
+ private java.nio.channels.FileLock _lock;
+
+ private boolean _locked;
+
+ private static Semaphore _semaphore = new Semaphore(1);
+
+
+ public static void main( final String[] args )
+ throws Exception {
+
+ List<Thread> threads = new ArrayList<Thread>();
+
+ for ( int i=0; i<3; i++ ) {
+
+ threads.add( new Thread() {
+ public void run() {
+ try {
+ FileLock lock = new FileLock( args[0] );
+
+ long start = System.currentTimeMillis();
+
+ lock.lock();
+ System.out.println( "time to lock:\t" + (System.currentTimeMillis()-start) );
+ Thread.sleep( Integer.parseInt( args[1] ) );
+ lock.unlock();
+ System.out.println( "total time:\t" + (System.currentTimeMillis()-start) );
+ }
+ catch ( Exception e ) {
+ e.printStackTrace();
+ }
+ }
+ } );
+ }
+
+ for ( Thread t : threads ) {
+ t.start();
+ }
+
+ for ( Thread t : threads ) {
+ t.join();
+ }
+
+ }
+}
diff --git a/buildscripts/emr/IOUtil.java b/buildscripts/emr/IOUtil.java
new file mode 100644
index 00000000000..8ee105c5155
--- /dev/null
+++ b/buildscripts/emr/IOUtil.java
@@ -0,0 +1,156 @@
+// IOUtil.java
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+public class IOUtil {
+
+ public static String urlFileName( String url ) {
+ int idx = url.lastIndexOf( "/" );
+ if ( idx < 0 )
+ return url;
+ return url.substring( idx + 1 );
+ }
+
+ public static long pipe( InputStream in , OutputStream out )
+ throws IOException {
+
+ long bytes = 0;
+
+ byte[] buf = new byte[2048];
+
+ while ( true ) {
+ int x = in.read( buf );
+ if ( x < 0 )
+ break;
+
+ bytes += x;
+ out.write( buf , 0 , x );
+ }
+
+ return bytes;
+ }
+
+ public static class PipingThread extends Thread {
+ public PipingThread( InputStream in , OutputStream out ) {
+ _in = in;
+ _out = out;
+
+ _wrote = 0;
+ }
+
+ public void run() {
+ try {
+ _wrote = pipe( _in , _out );
+ }
+ catch ( IOException ioe ) {
+ ioe.printStackTrace();
+ _wrote = -1;
+ }
+ }
+
+ public long wrote() {
+ return _wrote;
+ }
+
+ long _wrote;
+
+ final InputStream _in;
+ final OutputStream _out;
+ }
+
+ public static String readStringFully( InputStream in )
+ throws IOException {
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ pipe( in , bout );
+ return new String( bout.toByteArray() , "UTF8" );
+
+ }
+
+ public static Map<String,Object> readPythonSettings( File file )
+ throws IOException {
+
+ String all = readStringFully( new FileInputStream( file ) );
+
+ Map<String,Object> map = new TreeMap<String,Object>();
+
+ for ( String line : all.split( "\n" ) ) {
+ line = line.trim();
+ if ( line.length() == 0 )
+ continue;
+
+ String[] pcs = line.split( "=" );
+ if ( pcs.length != 2 )
+ continue;
+
+ String name = pcs[0].trim();
+ String value = pcs[1].trim();
+
+ if ( value.startsWith( "\"" ) ) {
+ map.put( name , value.substring( 1 , value.length() - 1 ) );
+ }
+ else {
+ map.put( name , Long.parseLong( value ) );
+ }
+
+ }
+
+ return map;
+ }
+
+ public static String[] runCommand( String cmd , File dir )
+ throws IOException {
+
+ Process p = Runtime.getRuntime().exec( cmd.split( " +" ) , new String[]{} , dir );
+ String[] results = new String[]{ IOUtil.readStringFully( p.getInputStream() ) , IOUtil.readStringFully( p.getErrorStream() ) };
+ try {
+ if ( p.waitFor() != 0 )
+ throw new RuntimeException( "command failed [" + cmd + "]\n" + results[0] + "\n" + results[1] );
+ }
+ catch ( InterruptedException ie ) {
+ throw new RuntimeException( "uh oh" );
+ }
+ return results;
+ }
+
+
+ public static void download( String http , File localDir )
+ throws IOException {
+
+ File f = localDir;
+ f.mkdirs();
+
+ f = new File( f.toString() + File.separator + urlFileName( http ) );
+
+ System.out.println( "downloading\n\t" + http + "\n\t" + f );
+
+ if ( f.exists() ) {
+ System.out.println( "\t already exists" );
+ return;
+ }
+
+ URL url = new URL( http );
+
+ InputStream in = url.openConnection().getInputStream();
+ OutputStream out = new FileOutputStream( f );
+
+ pipe( in , out );
+
+ out.close();
+ in.close();
+
+ }
+
+ public static void main( String[] args )
+ throws Exception {
+
+
+ byte[] data = new byte[]{ 'e' , 'r' , 'h' , 0 };
+ System.out.write( data );
+ System.out.println( "yo" );
+
+ }
+
+}
diff --git a/buildscripts/emr/MANIFEST.MF b/buildscripts/emr/MANIFEST.MF
new file mode 100644
index 00000000000..4a5b3f96691
--- /dev/null
+++ b/buildscripts/emr/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: emr
diff --git a/buildscripts/emr/emr.java b/buildscripts/emr/emr.java
new file mode 100644
index 00000000000..0f01d6d0e2d
--- /dev/null
+++ b/buildscripts/emr/emr.java
@@ -0,0 +1,380 @@
+// emr.java
+
+import java.io.*;
+import java.util.*;
+import java.net.*;
+
+import org.apache.hadoop.conf.*;
+import org.apache.hadoop.io.*;
+import org.apache.hadoop.mapred.*;
+import org.apache.hadoop.fs.*;
+
+
+public class emr {
+
+ static class MongoSuite {
+ String mongo;
+ String code;
+ String workingDir;
+
+ String suite;
+
+ void copy( MongoSuite c ) {
+ mongo = c.mongo;
+ code = c.code;
+ workingDir = c.workingDir;
+
+ suite = c.suite;
+
+ }
+
+ void downloadTo( File localDir )
+ throws IOException {
+ IOUtil.download( mongo , localDir );
+ IOUtil.download( code , localDir );
+ }
+
+ boolean runTest()
+ throws IOException {
+
+ // mkdir
+ File dir = new File( workingDir , suite );
+ dir.mkdirs();
+
+ // download
+ System.out.println( "going to download" );
+ downloadTo( dir );
+
+
+ // explode
+ System.out.println( "going to explode" );
+ IOUtil.runCommand( "tar zxvf " + IOUtil.urlFileName( code ) , dir );
+ String[] res = IOUtil.runCommand( "tar zxvf " + IOUtil.urlFileName( mongo ) , dir );
+ for ( String x : res[0].split( "\n" ) ) {
+ if ( x.indexOf( "/bin/" ) < 0 )
+ continue;
+ File f = new File( dir.toString() , x );
+ if ( ! f.renameTo( new File( dir , IOUtil.urlFileName( x ) ) ) )
+ throw new RuntimeException( "rename failed" );
+ }
+
+ List<String> cmd = new ArrayList<String>();
+ cmd.add( "/usr/bin/python" );
+ cmd.add( "buildscripts/smoke.py" );
+
+ File log_config = new File( dir , "log_config.py" );
+ System.out.println( "log_config: " + log_config.exists() );
+ if ( log_config.exists() ) {
+
+ java.util.Map<String,Object> properties = IOUtil.readPythonSettings( log_config );
+
+ cmd.add( "--buildlogger-builder" );
+ cmd.add( properties.get( "name" ).toString() );
+
+ cmd.add( "--buildlogger-buildnum" );
+ cmd.add( properties.get( "number" ).toString() );
+
+ cmd.add( "--buildlogger-credentials" );
+ cmd.add( "log_config.py" );
+
+ cmd.add( "--buildlogger-phase" );
+ {
+ int idx = suite.lastIndexOf( "/" );
+ if ( idx < 0 )
+ cmd.add( suite );
+ else
+ cmd.add( suite.substring( 0 , idx ) );
+ }
+
+ }
+
+ cmd.add( suite );
+
+ System.out.println( cmd );
+
+ Process p = Runtime.getRuntime().exec( cmd.toArray( new String[cmd.size()] ) , new String[]{} , dir );
+
+ List<Thread> threads = new ArrayList<Thread>();
+ threads.add( new IOUtil.PipingThread( p.getInputStream() , System.out ) );
+ threads.add( new IOUtil.PipingThread( p.getErrorStream() , System.out ) );
+
+ for ( Thread t : threads )
+ t.start();
+
+ try {
+ for ( Thread t : threads ) {
+ t.join();
+ }
+ int rc = p.waitFor();
+ return rc == 0;
+ }
+ catch ( InterruptedException ie ) {
+ ie.printStackTrace();
+ throw new RuntimeException( "sad" , ie );
+ }
+
+ }
+
+ public void readFields( DataInput in )
+ throws IOException {
+ mongo = in.readUTF();
+ code = in.readUTF();
+ workingDir = in.readUTF();
+
+ suite = in.readUTF();
+ }
+
+ public void write( final DataOutput out )
+ throws IOException {
+ out.writeUTF( mongo );
+ out.writeUTF( code );
+ out.writeUTF( workingDir );
+
+ out.writeUTF( suite );
+ }
+
+ public String toString() {
+ return "mongo: " + mongo + " code: " + code + " suite: " + suite + " workingDir: " + workingDir;
+ }
+ }
+
+ public static class Map implements Mapper<Text, MongoSuite, Text, IntWritable> {
+
+ public void map( Text key, MongoSuite value, OutputCollector<Text,IntWritable> output, Reporter reporter )
+ throws IOException {
+
+ FileLock lock = new FileLock( "mapper" );
+ try {
+ lock.lock();
+
+ System.out.println( "key: " + key );
+ System.out.println( "value: " + value );
+
+ long start = System.currentTimeMillis();
+ boolean passed = value.runTest();
+ long end = System.currentTimeMillis();
+
+ output.collect( new Text( passed ? "passed" : "failed" ) , new IntWritable( 1 ) );
+ output.collect( new Text( key.toString() + "-time-seconds" ) , new IntWritable( (int)((end-start)/(1000)) ) );
+ output.collect( new Text( key.toString() + "-passed" ) , new IntWritable( passed ? 1 : 0 ) );
+
+ String ip = IOUtil.readStringFully( new URL( "http://myip.10gen.com/" ).openConnection().getInputStream() );
+ ip = ip.substring( ip.indexOf( ":" ) + 1 ).trim();
+ output.collect( new Text( ip ) , new IntWritable(1) );
+ }
+ catch ( RuntimeException re ) {
+ re.printStackTrace();
+ throw re;
+ }
+ catch ( IOException ioe ) {
+ ioe.printStackTrace();
+ throw ioe;
+ }
+ finally {
+ lock.unlock();
+ }
+
+ }
+
+ public void configure(JobConf job) {}
+ public void close(){}
+ }
+
+ public static class Reduce implements Reducer<Text, IntWritable, Text, IntWritable> {
+
+ public void reduce( Text key, Iterator<IntWritable> values, OutputCollector<Text,IntWritable> output , Reporter reporter )
+ throws IOException {
+
+ int sum = 0;
+ while ( values.hasNext() ) {
+ sum += values.next().get();
+ }
+ output.collect( key , new IntWritable( sum ) );
+ }
+
+ public void configure(JobConf job) {}
+ public void close(){}
+ }
+
+ public static class MySplit implements InputSplit , Writable {
+
+ public MySplit(){
+ }
+
+ MySplit( MongoSuite config , int length ) {
+ _config = config;
+ _length = length;
+ }
+
+ public long getLength() {
+ return _length;
+ }
+
+ public String[] getLocations() {
+ return new String[0];
+ }
+
+ public void readFields( DataInput in )
+ throws IOException {
+ _config = new MongoSuite();
+ _config.readFields( in );
+ _length = in.readInt();
+ }
+
+ public void write( final DataOutput out )
+ throws IOException {
+ _config.write( out );
+ out.writeInt( _length );
+ }
+
+ MongoSuite _config;
+ int _length;
+ }
+
+ public static class InputMagic implements InputFormat<Text,MongoSuite> {
+
+ public RecordReader<Text,MongoSuite> getRecordReader( InputSplit split, JobConf job , Reporter reporter ){
+ final MySplit s = (MySplit)split;
+ return new RecordReader<Text,MongoSuite>() {
+
+ public void close(){}
+
+ public Text createKey() {
+ return new Text();
+ }
+
+ public MongoSuite createValue() {
+ return new MongoSuite();
+ }
+
+ public long getPos() {
+ return _seen ? 1 : 0;
+ }
+
+ public float getProgress() {
+ return getPos();
+ }
+
+ public boolean next( Text key , MongoSuite value ) {
+ key.set( s._config.suite );
+ value.copy( s._config );
+
+
+ boolean x = _seen;
+ _seen = true;
+ return !x;
+ }
+
+ boolean _seen = false;
+ };
+ }
+
+ public InputSplit[] getSplits( JobConf job , int numSplits ){
+ String[] pcs = job.get( "suites" ).split(",");
+ InputSplit[] splits = new InputSplit[pcs.length];
+ for ( int i=0; i<splits.length; i++ ) {
+ MongoSuite c = new MongoSuite();
+ c.suite = pcs[i];
+
+ c.mongo = job.get( "mongo" );
+ c.code = job.get( "code" );
+ c.workingDir = job.get( "workingDir" );
+
+ splits[i] = new MySplit( c , 100 /* XXX */);
+ }
+ return splits;
+ }
+
+ public void validateInput(JobConf job){}
+
+
+ }
+
+ /**
+ * args
+ * mongo tgz
+ * code tgz
+ * output path
+ * tests to run ?
+ */
+
+ public static void main( String[] args ) throws Exception{
+
+ JobConf conf = new JobConf();
+ conf.setJarByClass(emr.class);
+
+ String workingDir = "/data/db/emr/";
+
+
+ // parse args
+
+ int pos = 0;
+ for ( ; pos < args.length; pos++ ) {
+ if ( ! args[pos].startsWith( "--" ) )
+ break;
+
+ String arg = args[pos].substring(2);
+ if ( arg.equals( "workingDir" ) ) {
+ workingDir = args[++pos];
+ }
+ else {
+ System.err.println( "unknown arg: " + arg );
+ throw new RuntimeException( "unknown arg: " + arg );
+ }
+ }
+
+ String mongo = args[pos++];
+ String code = args[pos++];
+ String output = args[pos++];
+
+ String suites = "";
+ for ( ; pos < args.length; pos++ ) {
+ if ( suites.length() > 0 )
+ suites += ",";
+ suites += args[pos];
+ }
+
+ if ( suites.length() == 0 )
+ throw new RuntimeException( "no suites" );
+
+ System.out.println( "workingDir:\t" + workingDir );
+ System.out.println( "mongo:\t" + mongo );
+ System.out.println( "code:\t " + code );
+ System.out.println( "output\t: " + output );
+ System.out.println( "suites\t: " + suites );
+
+ if ( false ) {
+ MongoSuite s = new MongoSuite();
+ s.mongo = mongo;
+ s.code = code;
+ s.workingDir = workingDir;
+ s.suite = suites;
+ s.runTest();
+ return;
+ }
+
+ // main hadoop set
+ conf.set( "mongo" , mongo );
+ conf.set( "code" , code );
+ conf.set( "workingDir" , workingDir );
+ conf.set( "suites" , suites );
+
+ conf.set( "mapred.map.tasks" , "1" );
+ conf.setLong( "mapred.task.timeout" , 4 * 3600 * 1000 /* 4 hours */);
+
+ conf.setOutputKeyClass(Text.class);
+ conf.setOutputValueClass(IntWritable.class);
+
+ conf.setMapperClass(Map.class);
+ conf.setReducerClass(Reduce.class);
+
+ conf.setInputFormat(InputMagic.class);
+ conf.setOutputFormat(TextOutputFormat.class);
+
+ FileOutputFormat.setOutputPath(conf, new Path(output) );
+
+ // actually run
+
+ JobClient.runJob( conf );
+ }
+}
diff --git a/buildscripts/emr/emr.py b/buildscripts/emr/emr.py
new file mode 100644
index 00000000000..e060779cef1
--- /dev/null
+++ b/buildscripts/emr/emr.py
@@ -0,0 +1,385 @@
+
+import os
+import sys
+import shutil
+import datetime
+import time
+import subprocess
+import urllib
+import urllib2
+import json
+import pprint
+
+import boto
+import simples3
+
+import pymongo
+
+def findSettingsSetup():
+ sys.path.append( "./" )
+ sys.path.append( "../" )
+ sys.path.append( "../../" )
+ sys.path.append( "../../../" )
+
+findSettingsSetup()
+import settings
+import buildscripts.utils as utils
+import buildscripts.smoke as smoke
+
+bucket = simples3.S3Bucket( settings.emr_bucket , settings.emr_id , settings.emr_key )
+
+def _get_status():
+
+ def gh( cmds ):
+ txt = ""
+ for cmd in cmds:
+ res = utils.execsys( "git " + cmd )
+ txt = txt + res[0] + res[1]
+ return utils.md5string( txt )
+
+ return "%s-%s" % ( utils.execsys( "git describe" )[0].strip(), gh( [ "diff" , "status" ] ) )
+
+def _get_most_recent_tgz( prefix ):
+ # this is icky, but works for now
+ all = []
+ for x in os.listdir( "." ):
+ if not x.startswith( prefix ) or not x.endswith( ".tgz" ):
+ continue
+ all.append( ( x , os.stat(x).st_mtime ) )
+
+ if len(all) == 0:
+ raise Exception( "can't find file with prefix: " + prefix )
+
+ all.sort( lambda x,y: int(y[1] - x[1]) )
+
+ return all[0][0]
+
+def get_build_info():
+ return ( os.environ.get('MONGO_BUILDER_NAME') , os.environ.get('MONGO_BUILD_NUMBER') )
+
+def make_tarball():
+
+ m = _get_most_recent_tgz( "mongodb-" )
+
+ c = "test-code-emr.tgz"
+ tar = "tar zcf %s src jstests buildscripts" % c
+
+ log_config = "log_config.py"
+ if os.path.exists( log_config ):
+ os.unlink( log_config )
+
+ credentials = do_credentials()
+ if credentials:
+
+ builder , buildnum = get_build_info()
+
+ if builder and buildnum:
+
+ file = open( log_config , "wb" )
+ file.write( 'username="%s"\npassword="%s"\n' % credentials )
+ file.write( 'name="%s"\nnumber=%s\n'% ( builder , buildnum ) )
+
+ file.close()
+
+ tar = tar + " " + log_config
+
+ utils.execsys( tar )
+ return ( m , c )
+
+def _put_ine( bucket , local , remote ):
+ print( "going to put\n\t%s\n\thttp://%s.s3.amazonaws.com/%s" % ( local , settings.emr_bucket , remote ) )
+
+ for x in bucket.listdir( prefix=remote ):
+ print( "\talready existed" )
+ return remote
+
+ bucket.put( remote , open( local , "rb" ).read() , acl="public-read" )
+ return remote
+
+def build_jar():
+ root = "build/emrjar"
+ src = "buildscripts/emr"
+
+ if os.path.exists( root ):
+ shutil.rmtree( root )
+ os.makedirs( root )
+
+ for x in os.listdir( src ):
+ if not x.endswith( ".java" ):
+ continue
+ shutil.copyfile( src + "/" + x , root + "/" + x )
+ shutil.copyfile( src + "/MANIFEST.MF" , root + "/MANIFEST.FM" )
+
+ classpath = os.listdir( src + "/lib" )
+ for x in classpath:
+ shutil.copyfile( src + "/lib/" + x , root + "/" + x )
+ classpath.append( "." )
+ classpath = ":".join(classpath)
+
+ for x in os.listdir( root ):
+ if x.endswith( ".java" ):
+ if subprocess.call( [ "javac" , "-cp" , classpath , x ] , cwd=root) != 0:
+ raise Exception( "compiled failed" )
+
+ args = [ "jar" , "-cfm" , "emr.jar" , "MANIFEST.FM" ]
+ for x in os.listdir( root ):
+ if x.endswith( ".class" ):
+ args.append( x )
+ subprocess.call( args , cwd=root )
+
+ shutil.copyfile( root + "/emr.jar" , "emr.jar" )
+
+ return "emr.jar"
+
+def push():
+ mongo , test_code = make_tarball()
+ print( mongo )
+ print( test_code )
+
+ root = "emr/%s/%s" % ( datetime.date.today().strftime("%Y-%m-%d") , os.uname()[0].lower() )
+
+ def make_long_name(local,hash):
+ pcs = local.rpartition( "." )
+ h = _get_status()
+ if hash:
+ h = utils.md5sum( local )
+ return "%s/%s-%s.%s" % ( root , pcs[0] , h , pcs[2] )
+
+ mongo = _put_ine( bucket , mongo , make_long_name( mongo , False ) )
+ test_code = _put_ine( bucket , test_code , make_long_name( test_code , True ) )
+
+ jar = build_jar()
+ jar = _put_ine( bucket , jar , make_long_name( jar , False ) )
+
+ setup = "buildscripts/emr/emrnodesetup.sh"
+ setup = _put_ine( bucket , setup , make_long_name( setup , True ) )
+
+ return mongo , test_code , jar , setup
+
+def run_tests( things , tests ):
+ if len(tests) == 0:
+ raise Exception( "no tests" )
+ oldNum = len(tests)
+ tests = fix_suites( tests )
+ print( "tests expanded from %d to %d" % ( oldNum , len(tests) ) )
+
+ print( "things:%s\ntests:%s\n" % ( things , tests ) )
+
+ emr = boto.connect_emr( settings.emr_id , settings.emr_key )
+
+ def http(path):
+ return "http://%s.s3.amazonaws.com/%s" % ( settings.emr_bucket , path )
+
+ run_s3_path = "emr/%s/%s/%s/" % ( os.getenv( "USER" ) ,
+ os.getenv( "HOST" ) ,
+ datetime.datetime.today().strftime( "%Y%m%d-%H%M" ) )
+
+ run_s3_root = "s3n://%s/%s/" % ( settings.emr_bucket , run_s3_path )
+
+ out = run_s3_root + "out"
+ logs = run_s3_root + "logs"
+
+ jar="s3n://%s/%s" % ( settings.emr_bucket , things[2] )
+ step_args=[ http(things[0]) , http(things[1]) , out , ",".join(tests) ]
+
+ step = boto.emr.step.JarStep( "emr main" , jar=jar,step_args=step_args )
+ print( "jar:%s\nargs:%s" % ( jar , step_args ) )
+
+ setup = boto.emr.BootstrapAction( "setup" , "s3n://%s/%s" % ( settings.emr_bucket , things[3] ) , [] )
+
+ jobid = emr.run_jobflow( name = "Mongo EMR for %s from %s" % ( os.getenv( "USER" ) , os.getenv( "HOST" ) ) ,
+ ec2_keyname = "emr1" ,
+ slave_instance_type = "m1.large" ,
+ ami_version = "latest" ,
+ num_instances=5 ,
+ log_uri = logs ,
+ bootstrap_actions = [ setup ] ,
+ steps = [ step ] )
+
+
+ print( "%s jobid: %s" % ( datetime.datetime.today() , jobid ) )
+
+ while ( True ):
+ flow = emr.describe_jobflow( jobid )
+ print( "%s status: %s" % ( datetime.datetime.today() , flow.state ) )
+ if flow.state == "COMPLETED" or flow.state == "FAILED":
+ break
+ time.sleep(30)
+
+ syncdir = "build/emrout/" + jobid + "/"
+ sync_s3( run_s3_path , syncdir )
+
+ final_out = "build/emrout/" + jobid + "/"
+
+ print("output in: " + final_out )
+ do_output( final_out )
+
+def sync_s3( remote_dir , local_dir ):
+ for x in bucket.listdir( remote_dir ):
+ out = local_dir + "/" + x[0]
+
+ if os.path.exists( out ) and x[2].find( utils.md5sum( out ) ) >= 0:
+ continue
+
+ dir = out.rpartition( "/" )[0]
+ if not os.path.exists( dir ):
+ os.makedirs( dir )
+
+ thing = bucket.get( x[0] )
+ open( out , "wb" ).write( thing.read() )
+
+def fix_suites( suites ):
+ fixed = []
+ for name,x in smoke.expand_suites( suites , False ):
+ idx = name.find( "/jstests" )
+ if idx >= 0:
+ name = name[idx+1:]
+ fixed.append( name )
+ return fixed
+
+def do_credentials():
+ root = "buildbot.tac"
+
+ while len(root) < 40 :
+ if os.path.exists( root ):
+ break
+ root = "../" + root
+
+ if not os.path.exists( root ):
+ return None
+
+ credentials = {}
+ execfile(root, credentials, credentials)
+
+ if "slavename" not in credentials:
+ return None
+
+ if "passwd" not in credentials:
+ return None
+
+ return ( credentials["slavename"] , credentials["passwd"] )
+
+
+def do_output( dir ):
+
+ def go_down( start ):
+ lst = os.listdir(dir)
+ if len(lst) != 1:
+ raise Exception( "sad: " + start )
+ return start + "/" + lst[0]
+
+ while "out" not in os.listdir( dir ):
+ dir = go_down( dir )
+
+ dir = dir + "/out"
+
+ pieces = os.listdir(dir)
+ pieces.sort()
+
+ passed = []
+ failed = []
+ times = {}
+
+ for x in pieces:
+ if not x.startswith( "part" ):
+ continue
+ full = dir + "/" + x
+
+ for line in open( full , "rb" ):
+ if line.find( "-passed" ) >= 0:
+ passed.append( line.partition( "-passed" )[0] )
+ continue
+
+ if line.find( "-failed" ) >= 0:
+ failed.append( line.partition( "-failed" )[0] )
+ continue
+
+ if line.find( "-time-seconds" ) >= 0:
+ p = line.partition( "-time-seconds" )
+ times[p[0]] = p[2].strip()
+ continue
+
+ print( "\t" + line.strip() )
+
+ def print_list(name,lst):
+ print( name )
+ for x in lst:
+ print( "\t%s\t%s" % ( x , times[x] ) )
+
+ print_list( "passed" , passed )
+ print_list( "failed" , failed )
+
+ if do_credentials():
+ builder , buildnum = get_build_info()
+ if builder and buildnum:
+ conn = pymongo.Connection( "bbout1.10gen.cc" )
+ db = conn.buildlogs
+ q = { "builder" : builder , "buildnum" : int(buildnum) }
+ doc = db.builds.find_one( q )
+
+ if doc:
+ print( "\nhttp://buildlogs.mongodb.org/build/%s" % doc["_id"] )
+
+
+if __name__ == "__main__":
+ if len(sys.argv) == 1:
+ print( "need an arg" )
+
+ elif sys.argv[1] == "tarball":
+ make_tarball()
+ elif sys.argv[1] == "jar":
+ build_jar()
+ elif sys.argv[1] == "push":
+ print( push() )
+
+ elif sys.argv[1] == "sync":
+ sync_s3( sys.argv[2] , sys.argv[3] )
+
+ elif sys.argv[1] == "fix_suites":
+ for x in fix_suites( sys.argv[2:] ):
+ print(x)
+
+ elif sys.argv[1] == "credentials":
+ print( do_credentials() )
+
+ elif sys.argv[1] == "test":
+ m , c = make_tarball()
+ build_jar()
+ cmd = [ "java" , "-cp" , os.environ.get( "CLASSPATH" , "." ) + ":emr.jar" , "emr" ]
+
+ workingDir = "/data/emr/test"
+ cmd.append( "--workingDir" )
+ cmd.append( workingDir )
+ if os.path.exists( workingDir ):
+ shutil.rmtree( workingDir )
+
+ cmd.append( "file://" + os.getcwd() + "/" + m )
+ cmd.append( "file://" + os.getcwd() + "/" + c )
+
+ out = "/tmp/emrresults"
+ cmd.append( out )
+ if os.path.exists( out ):
+ shutil.rmtree( out )
+
+ cmd.append( "jstests/basic1.js" )
+
+ subprocess.call( cmd )
+
+ for x in os.listdir( out ):
+ if x.startswith( "." ):
+ continue
+ print( x )
+ for z in open( out + "/" + x ):
+ print( "\t" + z.strip() )
+
+ elif sys.argv[1] == "output":
+ do_output( sys.argv[2] )
+
+ elif sys.argv[1] == "full":
+ things = push()
+ run_tests( things , sys.argv[2:] )
+
+ else:
+ things = push()
+ run_tests( things , sys.argv[1:] )
+
diff --git a/buildscripts/emr/emrnodesetup.sh b/buildscripts/emr/emrnodesetup.sh
new file mode 100644
index 00000000000..546becf3e27
--- /dev/null
+++ b/buildscripts/emr/emrnodesetup.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+sudo mkdir /mnt/data
+sudo ln -s /mnt/data /data
+sudo chown hadoop /mnt/data
+
+sudo easy_install pymongo
diff --git a/buildscripts/errorcodes.py b/buildscripts/errorcodes.py
index dec1030ddad..ef5e1a88877 100755
--- a/buildscripts/errorcodes.py
+++ b/buildscripts/errorcodes.py
@@ -6,7 +6,7 @@ import re
import utils
-assertNames = [ "uassert" , "massert" ]
+assertNames = [ "uassert" , "massert", "fassert", "fassertFailed" ]
def assignErrorCodes():
cur = 10000
@@ -32,10 +32,15 @@ def assignErrorCodes():
codes = []
def readErrorCodes( callback, replaceZero = False ):
- ps = [ re.compile( "(([umsg]asser(t|ted))) *\(( *)(\d+)" ) ,
- re.compile( "((User|Msg|MsgAssertion)Exceptio(n))\(( *)(\d+)" ) ,
- re.compile( "(((verify))) *\(( *)(\d+)" )
+
+ quick = [ "assert" , "Exception"]
+
+ ps = [ re.compile( "(([umsgf]asser(t|ted))) *\(( *)(\d+)" ) ,
+ re.compile( "((User|Msg|MsgAssertion)Exceptio(n))\(( *)(\d+)" ),
+ re.compile( "((fassertFailed)()) *\(( *)(\d+)" )
]
+
+ bad = [ re.compile( "\sassert *\(" ) ]
for x in utils.getAllSourceFiles():
@@ -45,30 +50,46 @@ def readErrorCodes( callback, replaceZero = False ):
lineNum = 1
for line in open( x ):
-
- for p in ps:
-
- def repl( m ):
- m = m.groups()
-
- start = m[0]
- spaces = m[3]
- code = m[4]
- if code == '0' and replaceZero :
- code = getNextCode( lastCodes )
- lastCodes.append( code )
- code = str( code )
- needReplace[0] = True
-
- print( "Adding code " + code + " to line " + x + ":" + str( lineNum ) )
-
- else :
- codes.append( ( x , lineNum , line , code ) )
- callback( x , lineNum , line , code )
-
- return start + "(" + spaces + code
+
+ found = False
+ for zz in quick:
+ if line.find( zz ) >= 0:
+ found = True
+ break
+
+ if found:
- line = re.sub( p, repl, line )
+ if x.find( "src/mongo/" ) >= 0:
+ for b in bad:
+ if len(b.findall( line )) > 0:
+ print( x )
+ print( line )
+ raise Exception( "you can't use a bare assert" )
+
+ for p in ps:
+
+ def repl( m ):
+ m = m.groups()
+
+ start = m[0]
+ spaces = m[3]
+ code = m[4]
+ if code == '0' and replaceZero :
+ code = getNextCode( lastCodes )
+ lastCodes.append( code )
+ code = str( code )
+ needReplace[0] = True
+
+ print( "Adding code " + code + " to line " + x + ":" + str( lineNum ) )
+
+ else :
+ codes.append( ( x , lineNum , line , code ) )
+ callback( x , lineNum , line , code )
+
+ return start + "(" + spaces + code
+
+ line = re.sub( p, repl, line )
+ # end if ps loop
if replaceZero : lines.append( line )
lineNum = lineNum + 1
@@ -78,6 +99,7 @@ def readErrorCodes( callback, replaceZero = False ):
of = open( x + ".tmp", 'w' )
of.write( "".join( lines ) )
of.close()
+ os.remove(x)
os.rename( x + ".tmp", x )
diff --git a/buildscripts/hacks_mandriva.py b/buildscripts/hacks_mandriva.py
deleted file mode 100644
index d46170960cc..00000000000
--- a/buildscripts/hacks_mandriva.py
+++ /dev/null
@@ -1,9 +0,0 @@
-
-import os
-import glob
-
-def insert( env , options ):
- jslibPaths = glob.glob('/usr/include/js-*/')
- if len(jslibPaths) >= 1:
- jslibPath = jslibPaths.pop()
- env.Append( CPPPATH=[ jslibPath ] ) \ No newline at end of file
diff --git a/buildscripts/hacks_ubuntu.py b/buildscripts/hacks_ubuntu.py
deleted file mode 100644
index 3de1a6f0b7e..00000000000
--- a/buildscripts/hacks_ubuntu.py
+++ /dev/null
@@ -1,54 +0,0 @@
-
-import os
-
-def insert( env , options ):
-
- # now that sm is in the source tree, don't need this
- # if not foundxulrunner( env , options ):
- # if os.path.exists( "usr/include/mozjs/" ):
- # env.Append( CPPDEFINES=[ "MOZJS" ] )
-
- return
-
-def foundxulrunner( env , options ):
- best = None
-
- for x in os.listdir( "/usr/include" ):
- if x.find( "xulrunner" ) != 0:
- continue
- if x == "xulrunner":
- best = x
- break
- best = x
-
-
- if best is None:
- print( "warning: using ubuntu without xulrunner-dev. we recommend installing it" )
- return False
-
- incroot = "/usr/include/" + best + "/"
- libroot = "/usr/lib"
- if options["linux64"] and os.path.exists("/usr/lib64"):
- libroot += "64";
- libroot += "/" + best
-
-
- if not os.path.exists( libroot ):
- print( "warning: found xulrunner include but not lib for: " + best )
- return False
-
- env.Prepend( LIBPATH=[ libroot ] )
- env.Prepend( RPATH=[ libroot ] )
-
- env.Prepend( CPPPATH=[ incroot + "stable/" ,
- incroot + "unstable/" ,
- incroot ] )
- env.Prepend( CPPPATH=[ "/usr/include/nspr/" ] )
-
- env.Append( CPPDEFINES=[ "XULRUNNER" , "OLDJS" ] )
- if best.find( "1.9.0" ) >= 0 or best.endswith("1.9"):
- if best.endswith( "1.9.1.9" ):
- pass
- else:
- env.Append( CPPDEFINES=[ "XULRUNNER190" ] )
- return True
diff --git a/buildscripts/make_archive.py b/buildscripts/make_archive.py
new file mode 100755
index 00000000000..4c12e901a64
--- /dev/null
+++ b/buildscripts/make_archive.py
@@ -0,0 +1,116 @@
+#!/usr/bin/python
+
+'''Helper script for constructing an archive (zip or tar) from a list of files.
+
+The output format (tar, tgz, zip) is determined from the file name, unless the user specifies
+--format on the command line.
+
+This script simplifies the specification of filename transformations, so that, e.g.,
+src/mongo/foo.cpp and build/linux2/normal/buildinfo.cpp can get put into the same
+directory in the archive, perhaps mongodb-2.0.2/src/mongo.
+
+Usage:
+
+make_archive.py -o <output-file> [--format (tar|tgz|zip)] \
+ [--transform match1=replacement1 [--transform match2=replacement2 [...]]] \
+ <input file 1> [...]
+
+If the input file names start with "@", the file is expected to contain a list of
+whitespace-separated file names to include in the archive. This helps get around the Windows
+command line length limit.
+
+Transformations are processed in command-line order and are short-circuiting. So, if a file matches
+match1, it is never compared against match2 or later. Matches are just python startswith()
+comparisons.
+
+For a detailed usage example, see src/SConscript.client or src/mongo/SConscript.
+'''
+
+import optparse
+import os
+import sys
+
+def main(argv):
+ opts = parse_options(argv[1:])
+ archive = open_archive_for_write(opts.output_filename, opts.archive_format)
+ try:
+ for input_filename in opts.input_filenames:
+ archive.add(input_filename, arcname=get_preferred_filename(input_filename,
+ opts.transformations))
+ finally:
+ archive.close()
+
+def parse_options(args):
+ parser = optparse.OptionParser()
+ parser.add_option('-o', dest='output_filename', default=None,
+ help='Name of the archive to output.', metavar='FILE')
+ parser.add_option('--format', dest='archive_format', default=None,
+ choices=('zip', 'tar', 'tgz'),
+ help='Format of archive to create. '
+ 'If omitted, use the suffix of the output filename to decide.')
+ parser.add_option('--transform', action='append', dest='transformations', default=[])
+
+ (opts, input_filenames) = parser.parse_args(args)
+ opts.input_filenames = []
+
+ for input_filename in input_filenames:
+ if input_filename.startswith('@'):
+ opts.input_filenames.extend(open(input_filename[1:], 'r').read().split())
+ else:
+ opts.input_filenames.append(input_filename)
+
+ if opts.output_filename is None:
+ parser.error('-o switch is required')
+
+ if opts.archive_format is None:
+ if opts.output_filename.endswith('.zip'):
+ opts.archive_format = 'zip'
+ elif opts.output_filename.endswith('tar.gz') or opts.output_filename.endswith('.tgz'):
+ opts.archive_format = 'tgz'
+ elif opts.output_filename.endswith('.tar'):
+ opts.archive_format = 'tar'
+ else:
+ parser.error('Could not deduce archive format from output filename "%s"' %
+ opts.output_filename)
+
+ try:
+ opts.transformations = [
+ xform.replace(os.path.altsep or os.path.sep, os.path.sep).split('=', 1)
+ for xform in opts.transformations]
+ except Exception, e:
+ parser.error(e)
+
+ return opts
+
+def open_archive_for_write(filename, archive_format):
+ '''Open a tar or zip archive for write, with the given format, and return it.
+
+ The type of archive is determined by the "archive_format" parameter, which should be
+ "tar", "tgz" (for gzipped tar) or "zip".
+ '''
+
+ if archive_format in ('tar', 'tgz'):
+ import tarfile
+ mode = 'w'
+ if archive_format is 'tgz':
+ mode += '|gz'
+ return tarfile.open(filename, mode)
+ if archive_format is 'zip':
+ import zipfile
+ # Infuriatingly, Zipfile calls the "add" method "write", but they're otherwise identical,
+ # for our purposes. WrappedZipFile is a minimal adapter class.
+ class WrappedZipFile(zipfile.ZipFile):
+ def add(self, filename, arcname):
+ return self.write(filename, arcname)
+ return WrappedZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
+ raise ValueError('Unsupported archive format "%s"' % archive_format)
+
+def get_preferred_filename(input_filename, transformations):
+ for match, replace in transformations:
+ if input_filename.startswith(match):
+ return replace + input_filename[len(match):]
+ return input_filename
+
+if __name__ == '__main__':
+ main(sys.argv)
+ sys.exit(0)
diff --git a/buildscripts/moduleconfig.py b/buildscripts/moduleconfig.py
new file mode 100644
index 00000000000..42fc52094ca
--- /dev/null
+++ b/buildscripts/moduleconfig.py
@@ -0,0 +1,131 @@
+"""Utility functions for SCons to discover and configure
+MongoDB modules (sub-trees of db/modules/). This file exports
+two functions:
+
+ discover_modules, which returns a dictionary of module name
+ to the imported python module object for the module's
+ build.py file
+
+ configure_modules, which runs per-module configuration, and
+ is given the SCons environment, its own path, etc
+
+Each module must have a "build.py" script, which is expected to
+have a "configure" function, and optionally a "test" function
+if the module exposes per-module tests.
+"""
+
+__all__ = ('discover_modules', 'configure_modules', 'register_module_test')
+
+import imp
+from os import listdir, makedirs
+from os.path import abspath, dirname, join, isdir, isfile
+
+def discover_modules(mongo_root):
+ """Scan <mongo_root>/db/modules/ for directories that
+ look like MongoDB modules (i.e. they contain a "build.py"
+ file), and return a dictionary of module name (the directory
+ name) to build.py python modules.
+ """
+ found_modules = {}
+
+ module_root = abspath(join(mongo_root, 'db', 'modules'))
+ if not isdir(module_root):
+ return found_modules
+
+ for name in listdir(module_root):
+ root = join(module_root, name)
+ if '.' in name or not isdir(root):
+ continue
+
+ build_py = join(root, 'build.py')
+ module = None
+
+ if isfile(build_py):
+ print "adding module: %s" % name
+ fp = open(build_py, "r")
+ module = imp.load_module("module_" + name, fp, build_py, (".py", "r", imp.PY_SOURCE))
+ found_modules[name] = module
+ fp.close()
+
+ return found_modules
+
+def configure_modules(modules, conf, env):
+ """
+ Run the configure() function in the build.py python modules
+ for each module listed in the modules dictionary (as created
+ by discover_modules). The configure() function should use the
+ prepare the Mongo build system for building the module.
+
+ build.py files may specify a "customIncludes" flag, which, if
+ True, causes configure() to be called with three arguments:
+ the SCons Configure() object, the SCons environment, and an
+ empty list which should be modified in-place by the configure()
+ function; if false, configure() is called with only the first
+ two arguments, and the source files are discovered with a
+ glob against the <module_root>/src/*.cpp.
+
+ Returns a dictionary mapping module name to a list of source
+ files to be compiled for the module.
+ """
+ source_map = {}
+
+ for name, module in modules.items():
+ print "configuring module: %s" % name
+
+ root = dirname(module.__file__)
+ module_sources = []
+
+ if getattr(module, "customIncludes", False):
+ # then the module configures itself and its
+ # configure() takes 3 args
+ module.configure(conf, env, module_sources)
+ else:
+ # else we glob the files in the module's src/
+ # subdirectory, and its configure() takes 2 args
+ module.configure(conf, env)
+ module_sources.extend(env.Glob(join(root, "src/*.cpp")))
+
+ if not module_sources:
+ print "WARNING: no source files for module %s, module will not be built." % name
+ else:
+ source_map[name] = module_sources
+
+ _setup_module_tests_file(str(env.File(env['MODULETEST_LIST'])))
+
+ return source_map
+
+module_tests = []
+def register_module_test(*command):
+ """Modules can register tests as part of their configure(), which
+ are commands whose exit status indicates the success or failure of
+ the test.
+
+ Use this function from configure() like:
+
+ register_module_test('/usr/bin/python', '/path/to/module/tests/foo.py')
+ register_module_test('/bin/bash', '/path/to/module/tests/bar.sh')
+
+ The registered test commands can be run with "scons smokeModuleTests"
+ """
+ command = ' '.join(command)
+ module_tests.append(command)
+
+def _setup_module_tests_file(test_file):
+ """Modules' configure() functions may have called register_module_test,
+ in which case, we need to record the registered tests' commands into
+ a text file which smoke.py and SCons know how to work with.
+ """
+ if not module_tests:
+ return
+
+ folder = dirname(test_file)
+ if not isdir(folder):
+ makedirs(folder)
+
+ fp = file(test_file, 'w')
+ for test in module_tests:
+ fp.write(test)
+ fp.write('\n')
+ fp.close()
+ print "Generated %s" % test_file
+
diff --git a/buildscripts/packager.py b/buildscripts/packager.py
index 400239cfd54..d0dd492981a 100644
--- a/buildscripts/packager.py
+++ b/buildscripts/packager.py
@@ -376,7 +376,7 @@ def make_deb(distro, arch, spec, srcdir):
oldcwd=os.getcwd()
try:
os.chdir(sdir)
- sysassert(["dpkg-buildpackage", "-a"+distro_arch])
+ sysassert(["dpkg-buildpackage", "-a"+distro_arch, "-k Richard Kreuter <richard@10gen.com>"])
finally:
os.chdir(oldcwd)
r=distro.repodir(arch)
@@ -576,7 +576,7 @@ Description: An object/document-oriented database
"""
s=re.sub("@@PACKAGE_BASENAME@@", "mongodb%s" % spec.suffix(), s)
conflict_suffixes=["", "-stable", "-unstable", "-nightly", "-10gen", "-10gen-unstable"]
- conflict_suffixes.remove(spec.suffix())
+ conflict_suffixes = [suff for suff in conflict_suffixes if suff != spec.suffix()]
s=re.sub("@@PACKAGE_CONFLICTS@@", ", ".join(["mongodb"+suffix for suffix in conflict_suffixes]), s)
f=open(path, 'w')
try:
@@ -686,7 +686,8 @@ binary-arch: build install
#\tdh_installinfo
\tdh_installman
\tdh_link
-\tdh_strip
+# Appears to be broken on Ubuntu 11.10...?
+#\tdh_strip
\tdh_compress
\tdh_fixperms
\tdh_installdeb
@@ -901,8 +902,10 @@ fi
%{_bindir}/mongo
%{_bindir}/mongodump
%{_bindir}/mongoexport
-%{_bindir}/mongofiles
+#@@VERSION!=2.1.0@@%{_bindir}/mongofiles
%{_bindir}/mongoimport
+#@@VERSION>=2.1.0@@%{_bindir}/mongooplog
+#@@VERSION>=2.1.0@@%{_bindir}/mongoperf
%{_bindir}/mongorestore
#@@VERSION>1.9@@%{_bindir}/mongotop
%{_bindir}/mongostat
@@ -952,9 +955,9 @@ fi
s=re.sub("@@PACKAGE_REVISION@@", str(int(spec.param("revision"))+1) if spec.param("revision") else "1", s)
s=re.sub("@@BINARYDIR@@", BINARYDIR, s)
conflict_suffixes=["", "-10gen", "-10gen-unstable"]
- conflict_suffixes.remove(suffix)
+ conflict_suffixes = [suff for suff in conflict_suffixes if suff != spec.suffix()]
s=re.sub("@@PACKAGE_CONFLICTS@@", ", ".join(["mongo"+_ for _ in conflict_suffixes]), s)
- if suffix == "-10gen":
+ if suffix.endswith("-10gen"):
s=re.sub("@@PACKAGE_PROVIDES@@", "mongo-stable", s)
s=re.sub("@@PACKAGE_OBSOLETES@@", "mongo-stable", s)
elif suffix == "-10gen-unstable":
@@ -965,9 +968,25 @@ fi
lines=[]
for line in s.split("\n"):
- m = re.search("@@VERSION>(.*)@@(.*)", line)
- if m and spec.version_better_than(m.group(1)):
- lines.append(m.group(2))
+ m = re.search("@@VERSION(>|>=|!=)(\d.*)@@(.*)", line)
+ if m:
+ op = m.group(1)
+ ver = m.group(2)
+ fn = m.group(3)
+ if op == '>':
+ if spec.version_better_than(ver):
+ lines.append(fn)
+ elif op == '>=':
+ if spec.version() == ver or spec.version_better_than(ver):
+ lines.append(fn)
+ elif op == '!=':
+ if spec.version() != ver:
+ lines.append(fn)
+ else:
+ # Since we're inventing our own template system for RPM
+ # specfiles here, we oughtn't use template syntax we don't
+ # support.
+ raise Exception("BUG: probable bug in packager script: %s, %s, %s" % (m.group(1), m.group(2), m.group(3)))
else:
lines.append(line)
s="\n".join(lines)
diff --git a/buildscripts/s3md5.py b/buildscripts/s3md5.py
index 89800cd6898..3b0d0d82917 100644
--- a/buildscripts/s3md5.py
+++ b/buildscripts/s3md5.py
@@ -18,7 +18,7 @@ def check_dir( bucket , prefix ):
zips = {}
md5s = {}
for ( key , modify , etag , size ) in bucket.listdir( prefix=prefix ):
- if key.endswith( ".tgz" ) or key.endswith( ".zip" ):
+ if key.endswith( ".tgz" ) or key.endswith( ".zip" ) or key.endswith( ".tar.gz" ):
zips[key] = etag.replace( '"' , '' )
elif key.endswith( ".md5" ):
md5s[key] = True
@@ -40,7 +40,7 @@ def run():
bucket = simples3.S3Bucket( settings.bucket , settings.id , settings.key )
- for x in [ "osx" , "linux" , "win32" , "sunos5" ]:
+ for x in [ "osx" , "linux" , "win32" , "sunos5" , "src" ]:
check_dir( bucket , x )
diff --git a/buildscripts/smoke.py b/buildscripts/smoke.py
index c46b5d1879d..7740756b42f 100755
--- a/buildscripts/smoke.py
+++ b/buildscripts/smoke.py
@@ -33,14 +33,14 @@
# off all mongods on a box, which means you can't run two smoke.py
# jobs on the same host at once. So something's gotta change.
-from __future__ import with_statement
-
+from datetime import datetime
import glob
from optparse import OptionParser
import os
import parser
import re
import shutil
+import shlex
import socket
from subprocess import (Popen,
PIPE,
@@ -49,20 +49,29 @@ import sys
import time
from pymongo import Connection
+from pymongo.errors import OperationFailure
import utils
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
# TODO clean this up so we don't need globals...
mongo_repo = os.getcwd() #'./'
+failfile = os.path.join(mongo_repo, 'failfile.smoke')
test_path = None
mongod_executable = None
mongod_port = None
shell_executable = None
continue_on_failure = None
+file_of_commands_mode = False
tests = []
winners = []
losers = {}
+fails = [] # like losers but in format of tests
# For replication hash checking
replicated_collections = []
@@ -72,6 +81,7 @@ screwy_in_slave = {}
smoke_db_prefix = ''
small_oplog = False
+small_oplog_rs = False
# This class just implements the with statement API, for a sneaky
# purpose below.
@@ -81,10 +91,24 @@ class Nothing(object):
def __exit__(self, type, value, traceback):
return not isinstance(value, Exception)
+def buildlogger(cmd, is_global=False):
+ # if the environment variable MONGO_USE_BUILDLOGGER
+ # is set to 'true', then wrap the command with a call
+ # to buildlogger.py, which sends output to the buidlogger
+ # machine; otherwise, return as usual.
+ if os.environ.get('MONGO_USE_BUILDLOGGER', '').lower().strip() == 'true':
+ if is_global:
+ return [utils.find_python(), 'buildscripts/buildlogger.py', '-g'] + cmd
+ else:
+ return [utils.find_python(), 'buildscripts/buildlogger.py'] + cmd
+ return cmd
+
+
class mongod(object):
def __init__(self, **kwargs):
self.kwargs = kwargs
self.proc = None
+ self.auth = False
def __enter__(self):
self.start()
@@ -122,6 +146,15 @@ class mongod(object):
print >> sys.stderr, "timeout starting mongod"
return False
+ def setup_admin_user(self, port=mongod_port):
+ try:
+ Connection( "localhost" , int(port) ).admin.add_user("admin","password")
+ except OperationFailure, e:
+ if e.message == 'need to login':
+ pass # SERVER-4225
+ else:
+ raise e
+
def start(self):
global mongod_port
global mongod
@@ -139,25 +172,34 @@ class mongod(object):
self.slave = True
if os.path.exists(dir_name):
if 'slave' in self.kwargs:
- argv = ["python", "buildscripts/cleanbb.py", '--nokill', dir_name]
+ argv = [utils.find_python(), "buildscripts/cleanbb.py", '--nokill', dir_name]
else:
- argv = ["python", "buildscripts/cleanbb.py", dir_name]
+ argv = [utils.find_python(), "buildscripts/cleanbb.py", dir_name]
call(argv)
utils.ensureDir(dir_name)
argv = [mongod_executable, "--port", str(self.port), "--dbpath", dir_name]
if self.kwargs.get('small_oplog'):
- argv += ["--master", "--oplogSize", "256"]
+ argv += ["--master", "--oplogSize", "511"]
+ if self.kwargs.get('small_oplog_rs'):
+ argv += ["--replSet", "foo", "--oplogSize", "511"]
if self.slave:
argv += ['--slave', '--source', 'localhost:' + str(srcport)]
if self.kwargs.get('no_journal'):
argv += ['--nojournal']
if self.kwargs.get('no_preallocj'):
argv += ['--nopreallocj']
+ if self.kwargs.get('auth'):
+ argv += ['--auth']
+ self.auth = True
print "running " + " ".join(argv)
- self.proc = Popen(argv)
+ self.proc = self._start(buildlogger(argv, is_global=True))
+
if not self.did_mongod_start(self.port):
raise Exception("Failed to start mongod")
+ if self.auth:
+ self.setup_admin_user(self.port)
+
if self.slave:
local = Connection(port=self.port, slave_okay=True).local
synced = False
@@ -166,20 +208,53 @@ class mongod(object):
for source in local.sources.find(fields=["syncedTo"]):
synced = synced and "syncedTo" in source and source["syncedTo"]
+ def _start(self, argv):
+ """In most cases, just call subprocess.Popen(). On windows,
+ add the started process to a new Job Object, so that any
+ child processes of this process can be killed with a single
+ call to TerminateJobObject (see self.stop()).
+ """
+ proc = Popen(argv)
+
+ if os.sys.platform == "win32":
+ # Create a job object with the "kill on job close"
+ # flag; this is inherited by child processes (ie
+ # the mongod started on our behalf by buildlogger)
+ # and lets us terminate the whole tree of processes
+ # rather than orphaning the mongod.
+ import win32job
+
+ self.job_object = win32job.CreateJobObject(None, '')
+
+ job_info = win32job.QueryInformationJobObject(
+ self.job_object, win32job.JobObjectExtendedLimitInformation)
+ job_info['BasicLimitInformation']['LimitFlags'] |= win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
+ win32job.SetInformationJobObject(
+ self.job_object,
+ win32job.JobObjectExtendedLimitInformation,
+ job_info)
+
+ win32job.AssignProcessToJobObject(self.job_object, proc._handle)
+
+ return proc
+
def stop(self):
if not self.proc:
print >> sys.stderr, "probable bug: self.proc unset in stop()"
return
try:
- # This function not available in Python 2.5
- self.proc.terminate()
- except AttributeError:
if os.sys.platform == "win32":
- import win32process
- win32process.TerminateProcess(self.proc._handle, -1)
+ import win32job
+ win32job.TerminateJobObject(self.job_object, -1)
+ import time
+ # Windows doesn't seem to kill the process immediately, so give it some time to die
+ time.sleep(5)
else:
- from os import kill
- kill(self.proc.pid, 15)
+ # This function not available in Python 2.5
+ self.proc.terminate()
+ except AttributeError:
+ from os import kill
+ kill(self.proc.pid, 15)
self.proc.wait()
sys.stderr.flush()
sys.stdout.flush()
@@ -241,12 +316,31 @@ def check_db_hashes(master, slave):
lost_in_master.append(db)
+def ternary( b , l="true", r="false" ):
+ if b:
+ return l
+ return r
+
# Blech.
def skipTest(path):
- if small_oplog:
- if os.path.basename(path) in ["cursor8.js", "indexh.js", "dropdb.js"]:
+ basename = os.path.basename(path)
+ parentDir = os.path.basename(os.path.dirname(path))
+ if small_oplog: # For tests running in parallel
+ if basename in ["cursor8.js", "indexh.js", "dropdb.js"]:
+ return True
+ if auth or keyFile: # For tests running with auth
+ # Skip any tests that run with auth explicitly
+ if parentDir == "auth" or "auth" in basename or parentDir == "tool": # SERVER-6368
+ return True
+ # These tests don't pass with authentication due to limitations of the test infrastructure,
+ # not due to actual bugs.
+ if os.path.join(parentDir,basename) in ["sharding/sync3.js", "sharding/sync6.js", "sharding/parallel.js", "jstests/bench_test1.js", "jstests/bench_test2.js", "jstests/bench_test3.js"]:
+ return True
+ # These tests fail due to bugs
+ if os.path.join(parentDir,basename) in ["sharding/sync_conn_cmd.js"]:
return True
+
return False
def runTest(test):
@@ -259,11 +353,22 @@ def runTest(test):
if skipTest(path):
print "skipping " + path
return
- if ext == ".js":
+ if file_of_commands_mode:
+ # smoke.py was invoked like "--mode files --from-file foo",
+ # so don't try to interpret the test path too much
+ if os.sys.platform == "win32":
+ argv = [path]
+ else:
+ argv = shlex.split(path)
+ path = argv[0]
+ # if the command is a python script, use the script name
+ if os.path.basename(path) in ('python', 'python.exe'):
+ path = argv[1]
+ elif ext == ".js":
argv = [shell_executable, "--port", mongod_port]
if not usedb:
argv += ["--nodb"]
- if small_oplog:
+ if small_oplog or small_oplog_rs:
argv += ["--eval", 'testingReplication = true;']
argv += [path]
elif ext in ["", ".exe"]:
@@ -271,36 +376,68 @@ def runTest(test):
if os.path.basename(path) in ["test", "test.exe", "perftest", "perftest.exe"]:
argv = [path]
# more blech
- elif os.path.basename(path) == 'mongos':
+ elif os.path.basename(path) in ['mongos', 'mongos.exe']:
argv = [path, "--test"]
else:
argv = [test_path and os.path.abspath(os.path.join(test_path, path)) or path,
"--port", mongod_port]
else:
raise Bug("fell off in extenstion case: %s" % path)
- sys.stderr.write( "starting test : %s \n" % os.path.basename(path) )
- sys.stderr.flush()
- print " *******************************************"
- print " Test : " + os.path.basename(path) + " ..."
- t1 = time.time()
+
+ if keyFile:
+ f = open(keyFile, 'r')
+ keyFileData = re.sub(r'\s', '', f.read()) # Remove all whitespace
+ f.close()
+ else:
+ keyFileData = None
+
+
+ # sys.stdout.write() is more atomic than print, so using it prevents
+ # lines being interrupted by, e.g., child processes
+ sys.stdout.write(" *******************************************\n")
+ sys.stdout.write(" Test : %s ...\n" % os.path.basename(path))
+ sys.stdout.flush()
+
# FIXME: we don't handle the case where the subprocess
# hangs... that's bad.
- if argv[0].endswith( 'mongo' ) and not '--eval' in argv :
- argv = argv + [ '--eval', 'TestData = new Object();' +
- 'TestData.testPath = "' + path + '";' +
- 'TestData.testFile = "' + os.path.basename( path ) + '";' +
- 'TestData.testName = "' + re.sub( ".js$", "", os.path.basename( path ) ) + '";' +
- 'TestData.noJournal = ' + ( 'true' if no_journal else 'false' ) + ";" +
- 'TestData.noJournalPrealloc = ' + ( 'true' if no_preallocj else 'false' ) + ";" ]
+ if ( argv[0].endswith( 'mongo' ) or argv[0].endswith( 'mongo.exe' ) ) and not '--eval' in argv :
+ evalString = 'TestData = new Object();' + \
+ 'TestData.testPath = "' + path + '";' + \
+ 'TestData.testFile = "' + os.path.basename( path ) + '";' + \
+ 'TestData.testName = "' + re.sub( ".js$", "", os.path.basename( path ) ) + '";' + \
+ 'TestData.noJournal = ' + ternary( no_journal ) + ";" + \
+ 'TestData.noJournalPrealloc = ' + ternary( no_preallocj ) + ";" + \
+ 'TestData.auth = ' + ternary( auth ) + ";" + \
+ 'TestData.keyFile = ' + ternary( keyFile , '"' + str(keyFile) + '"' , 'null' ) + ";" + \
+ 'TestData.keyFileData = ' + ternary( keyFile , '"' + str(keyFileData) + '"' , 'null' ) + ";"
+ if os.sys.platform == "win32":
+ # double quotes in the evalString on windows; this
+ # prevents the backslashes from being removed when
+ # the shell (i.e. bash) evaluates this string. yuck.
+ evalString = evalString.replace('\\', '\\\\')
+
+ if auth and usedb:
+ evalString += 'jsTest.authenticate(db.getMongo());'
+
+ argv = argv + [ '--eval', evalString]
if argv[0].endswith( 'test' ) and no_preallocj :
argv = argv + [ '--nopreallocj' ]
- print argv
- r = call(argv, cwd=test_path)
+ sys.stdout.write(" Command : %s\n" % ' '.join(argv))
+ sys.stdout.write(" Date : %s\n" % datetime.now().ctime())
+ sys.stdout.flush()
+
+ os.environ['MONGO_TEST_FILENAME'] = os.path.basename(path)
+ t1 = time.time()
+ r = call(buildlogger(argv), cwd=test_path)
t2 = time.time()
- print " " + str((t2 - t1) * 1000) + "ms"
+ del os.environ['MONGO_TEST_FILENAME']
+
+ sys.stdout.write(" %fms\n" % ((t2 - t1) * 1000))
+ sys.stdout.flush()
+
if r != 0:
raise TestExitFailure(path, r)
@@ -317,17 +454,49 @@ def run_tests(tests):
# dbpath, etc., and so long as we shut ours down properly,
# starting this mongod shouldn't break anything, though.)
- # The reason we use with is so that we get __exit__ semantics
+ # The reason we want to use "with" is so that we get __exit__ semantics
+ # but "with" is only supported on Python 2.5+
- with mongod(small_oplog=small_oplog,no_journal=no_journal,no_preallocj=no_preallocj) as master:
- with mongod(slave=True) if small_oplog else Nothing() as slave:
- if small_oplog:
+ master = mongod(small_oplog_rs=small_oplog_rs,small_oplog=small_oplog,no_journal=no_journal,no_preallocj=no_preallocj,auth=auth).__enter__()
+ try:
+ if small_oplog:
+ slave = mongod(slave=True).__enter__()
+ elif small_oplog_rs:
+ slave = mongod(slave=True,small_oplog_rs=small_oplog_rs,small_oplog=small_oplog,no_journal=no_journal,no_preallocj=no_preallocj,auth=auth).__enter__()
+ primary = Connection(port=master.port, slave_okay=True);
+
+ primary.admin.command({'replSetInitiate' : {'_id' : 'foo', 'members' : [
+ {'_id': 0, 'host':'localhost:%s' % master.port},
+ {'_id': 1, 'host':'localhost:%s' % slave.port,'priority':0}]}})
+
+ ismaster = False
+ while not ismaster:
+ result = primary.admin.command("ismaster");
+ ismaster = result["ismaster"]
+ time.sleep(1)
+ else:
+ slave = Nothing()
+
+ try:
+ if small_oplog or small_oplog_rs:
master.wait_for_repl()
- for test in tests:
+ tests_run = 0
+ for tests_run, test in enumerate(tests):
try:
+ fails.append(test)
runTest(test)
+ fails.pop()
winners.append(test)
+
+ if small_oplog or small_oplog_rs:
+ master.wait_for_repl()
+ elif test[1]: # reach inside test and see if startmongod is true
+ if (tests_run+1) % 20 == 0:
+ # restart mongo every 20 times, for our 32-bit machines
+ master.__exit__(None, None, None)
+ master = mongod(small_oplog_rs=small_oplog_rs,small_oplog=small_oplog,no_journal=no_journal,no_preallocj=no_preallocj,auth=auth).__enter__()
+
except TestFailure, f:
try:
print f
@@ -341,12 +510,15 @@ def run_tests(tests):
return 1
if isinstance(slave, mongod):
check_db_hashes(master, slave)
-
+ finally:
+ slave.__exit__(None, None, None)
+ finally:
+ master.__exit__(None, None, None)
return 0
def report():
- print "%d test%s succeeded" % (len(winners), '' if len(winners) == 1 else 's')
+ print "%d tests succeeded" % len(winners)
num_missed = len(tests) - (len(winners) + len(losers.keys()))
if num_missed:
print "%d tests didn't get run" % num_missed
@@ -368,18 +540,35 @@ at the end of testing:""" % (src, dst)
at the end of testing:"""
for db in screwy_in_slave.keys():
print "%s\t %s" % (db, screwy_in_slave[db])
- if small_oplog and not (lost_in_master or lost_in_slave or screwy_in_slave):
+ if (small_oplog or small_oplog_rs) and not (lost_in_master or lost_in_slave or screwy_in_slave):
print "replication ok for %d collections" % (len(replicated_collections))
if losers or lost_in_slave or lost_in_master or screwy_in_slave:
raise Exception("Test failures")
-
-def expand_suites(suites):
+suiteGlobalConfig = {"js": ("[!_]*.js", True),
+ "quota": ("quota/*.js", True),
+ "jsPerf": ("perf/*.js", True),
+ "disk": ("disk/*.js", True),
+ "jsSlowNightly": ("slowNightly/*.js", True),
+ "jsSlowWeekly": ("slowWeekly/*.js", False),
+ "parallel": ("parallel/*.js", True),
+ "clone": ("clone/*.js", False),
+ "repl": ("repl/*.js", False),
+ "replSets": ("replsets/*.js", False),
+ "dur": ("dur/*.js", False),
+ "auth": ("auth/*.js", False),
+ "sharding": ("sharding/*.js", False),
+ "tool": ("tool/*.js", False),
+ "aggregation": ("aggregation/*.js", True),
+ "multiVersion": ("multiVersion/*.js", True )
+ }
+
+def expand_suites(suites,expandUseDB=True):
globstr = None
tests = []
for suite in suites:
if suite == 'all':
- return expand_suites(['test', 'perf', 'client', 'js', 'jsPerf', 'jsSlowNightly', 'jsSlowWeekly', 'parallel', 'clone', 'parallel', 'repl', 'auth', 'sharding', 'tool'])
+ return expand_suites(['test', 'perf', 'client', 'js', 'jsPerf', 'jsSlowNightly', 'jsSlowWeekly', 'clone', 'parallel', 'repl', 'auth', 'sharding', 'tool'],expandUseDB=expandUseDB)
if suite == 'test':
if os.sys.platform == "win32":
program = 'test.exe'
@@ -405,31 +594,31 @@ def expand_suites(suites):
program = 'mongos'
tests += [(os.path.join(mongo_repo, program), False)]
elif os.path.exists( suite ):
- tests += [ ( os.path.join( mongo_repo , suite ) , True ) ]
+ usedb = True
+ for name in suiteGlobalConfig:
+ if suite in glob.glob( "jstests/" + suiteGlobalConfig[name][0] ):
+ usedb = suiteGlobalConfig[name][1]
+ break
+ tests += [ ( os.path.join( mongo_repo , suite ) , usedb ) ]
else:
try:
- globstr, usedb = {"js": ("[!_]*.js", True),
- "quota": ("quota/*.js", True),
- "jsPerf": ("perf/*.js", True),
- "disk": ("disk/*.js", True),
- "jsSlowNightly": ("slowNightly/*.js", True),
- "jsSlowWeekly": ("slowWeekly/*.js", True),
- "parallel": ("parallel/*.js", True),
- "clone": ("clone/*.js", False),
- "repl": ("repl/*.js", False),
- "replSets": ("replsets/*.js", False),
- "dur": ("dur/*.js", False),
- "auth": ("auth/*.js", False),
- "sharding": ("sharding/*.js", False),
- "tool": ("tool/*.js", False)}[suite]
+ globstr, usedb = suiteGlobalConfig[suite]
except KeyError:
raise Exception('unknown test suite %s' % suite)
if globstr:
- globstr = os.path.join(mongo_repo, (os.path.join(('jstests/' if globstr.endswith('.js') else ''), globstr)))
- paths = glob.glob(globstr)
- paths.sort()
- tests += [(path, usedb) for path in paths]
+ if usedb and not expandUseDB:
+ tests += [ (suite,False) ]
+ else:
+ if globstr.endswith('.js'):
+ loc = 'jstests/'
+ else:
+ loc = ''
+ globstr = os.path.join(mongo_repo, (os.path.join(loc, globstr)))
+ globstr = os.path.normpath(globstr)
+ paths = glob.glob(globstr)
+ paths.sort()
+ tests += [(path, usedb) for path in paths]
return tests
@@ -438,8 +627,118 @@ def add_exe(e):
e += ".exe"
return e
+def set_globals(options, tests):
+ global mongod_executable, mongod_port, shell_executable, continue_on_failure, small_oplog, small_oplog_rs, no_journal, no_preallocj, auth, keyFile, smoke_db_prefix, test_path
+ global file_of_commands_mode
+ #Careful, this can be called multiple times
+ test_path = options.test_path
+
+ mongod_executable = add_exe(options.mongod_executable)
+ if not os.path.exists(mongod_executable):
+ raise Exception("no mongod found in this directory.")
+
+ mongod_port = options.mongod_port
+
+ shell_executable = add_exe( options.shell_executable )
+ if not os.path.exists(shell_executable):
+ raise Exception("no mongo shell found in this directory.")
+
+ continue_on_failure = options.continue_on_failure
+ smoke_db_prefix = options.smoke_db_prefix
+ small_oplog = options.small_oplog
+ if hasattr(options, "small_oplog_rs"):
+ small_oplog_rs = options.small_oplog_rs
+ no_journal = options.no_journal
+ no_preallocj = options.no_preallocj
+ if options.mode == 'suite' and tests == ['client']:
+ # The client suite doesn't work with authentication
+ if options.auth:
+ print "Not running client suite with auth even though --auth was provided"
+ auth = False;
+ keyFile = False;
+ else:
+ auth = options.auth
+ keyFile = options.keyFile
+
+ if auth and not keyFile:
+ # if only --auth was given to smoke.py, load the
+ # default keyFile from jstests/libs/authTestsKey
+ keyFile = os.path.join(mongo_repo, 'jstests', 'libs', 'authTestsKey')
+
+ # if smoke.py is running a list of commands read from a
+ # file (or stdin) rather than running a suite of js tests
+ file_of_commands_mode = options.File and options.mode == 'files'
+
+def clear_failfile():
+ if os.path.exists(failfile):
+ os.remove(failfile)
+
+def run_old_fails():
+ global tests
+
+ try:
+ f = open(failfile, 'r')
+ testsAndOptions = pickle.load(f)
+ f.close()
+ except Exception:
+ try:
+ f.close()
+ except:
+ pass
+ clear_failfile()
+ return # This counts as passing so we will run all tests
+
+ tests = [x[0] for x in testsAndOptions]
+ passed = []
+ try:
+ for (i, (test, options)) in enumerate(testsAndOptions):
+ # SERVER-5102: until we can figure out a better way to manage
+ # dependencies of the --only-old-fails build phase, just skip
+ # tests which we can't safely run at this point
+ path, usedb = test
+
+ if not os.path.exists(path):
+ passed.append(i)
+ winners.append(test)
+ continue
+
+ filename = os.path.basename(path)
+ if filename in ('test', 'test.exe') or filename.endswith('.js'):
+ set_globals(options, [filename])
+ oldWinners = len(winners)
+ run_tests([test])
+ if len(winners) != oldWinners: # can't use return value due to continue_on_failure
+ passed.append(i)
+ finally:
+ for offset, i in enumerate(passed):
+ testsAndOptions.pop(i - offset)
+
+ if testsAndOptions:
+ f = open(failfile, 'w')
+ pickle.dump(testsAndOptions, f)
+ else:
+ clear_failfile()
+
+ report() # exits with failure code if there is an error
+
+def add_to_failfile(tests, options):
+ try:
+ f = open(failfile, 'r')
+ testsAndOptions = pickle.load(f)
+ except Exception:
+ testsAndOptions = []
+
+ for test in tests:
+ if (test, options) not in testsAndOptions:
+ testsAndOptions.append( (test, options) )
+
+ f = open(failfile, 'w')
+ pickle.dump(testsAndOptions, f)
+
+
+
def main():
- global mongod_executable, mongod_port, shell_executable, continue_on_failure, small_oplog, no_journal, no_preallocj, smoke_db_prefix, test_path
+ global mongod_executable, mongod_port, shell_executable, continue_on_failure, small_oplog, no_journal, no_preallocj, auth, keyFile, smoke_db_prefix, test_path
parser = OptionParser(usage="usage: smoke.py [OPTIONS] ARGS*")
parser.add_option('--mode', dest='mode', default='suite',
help='If "files", ARGS are filenames; if "suite", ARGS are sets of tests (%default)')
@@ -451,7 +750,7 @@ def main():
"currently only used for 'client' (%default)")
parser.add_option('--mongod', dest='mongod_executable', default=os.path.join(mongo_repo, 'mongod'),
help='Path to mongod to run (%default)')
- parser.add_option('--port', dest='mongod_port', default="32000",
+ parser.add_option('--port', dest='mongod_port', default="27999",
help='Port the mongod will bind to (%default)')
parser.add_option('--mongo', dest='shell_executable', default=os.path.join(mongo_repo, 'mongo'),
help='Path to mongo, for .js test files (%default)')
@@ -465,55 +764,96 @@ def main():
parser.add_option('--small-oplog', dest='small_oplog', default=False,
action="store_true",
help='Run tests with master/slave replication & use a small oplog')
+ parser.add_option('--small-oplog-rs', dest='small_oplog_rs', default=False,
+ action="store_true",
+ help='Run tests with replica set replication & use a small oplog')
parser.add_option('--nojournal', dest='no_journal', default=False,
action="store_true",
help='Do not turn on journaling in tests')
parser.add_option('--nopreallocj', dest='no_preallocj', default=False,
action="store_true",
help='Do not preallocate journal files in tests')
+ parser.add_option('--auth', dest='auth', default=False,
+ action="store_true",
+ help='Run standalone mongods in tests with authentication enabled')
+ parser.add_option('--keyFile', dest='keyFile', default=None,
+ help='Path to keyFile to use to run replSet and sharding tests with authentication enabled')
+ parser.add_option('--ignore', dest='ignore_files', default=None,
+ help='Pattern of files to ignore in tests')
+ parser.add_option('--only-old-fails', dest='only_old_fails', default=False,
+ action="store_true",
+ help='Check the failfile and only run all tests that failed last time')
+ parser.add_option('--reset-old-fails', dest='reset_old_fails', default=False,
+ action="store_true",
+ help='Clear the failfile. Do this if all tests pass')
+ parser.add_option('--with-cleanbb', dest='with_cleanbb', default=False,
+ action="store_true",
+ help='Clear database files from previous smoke.py runs')
+
+ # Buildlogger invocation from command line
+ parser.add_option('--buildlogger-builder', dest='buildlogger_builder', default=None,
+ action="store", help='Set the "builder name" for buildlogger')
+ parser.add_option('--buildlogger-buildnum', dest='buildlogger_buildnum', default=None,
+ action="store", help='Set the "build number" for buildlogger')
+ parser.add_option('--buildlogger-credentials', dest='buildlogger_credentials', default=None,
+ action="store", help='Path to Python file containing buildlogger credentials')
+ parser.add_option('--buildlogger-phase', dest='buildlogger_phase', default=None,
+ action="store", help='Set the "phase" for buildlogger (e.g. "core", "auth") for display in the webapp (optional)')
+
global tests
(options, tests) = parser.parse_args()
- print tests
-
- test_path = options.test_path
-
- mongod_executable = add_exe(options.mongod_executable)
- if not os.path.exists(mongod_executable):
- raise Exception("no mongod found in this directory.")
-
- mongod_port = options.mongod_port
-
- shell_executable = add_exe( options.shell_executable )
- if not os.path.exists(shell_executable):
- raise Exception("no mongo shell found in this directory.")
+ set_globals(options, tests)
- continue_on_failure = options.continue_on_failure
- smoke_db_prefix = options.smoke_db_prefix
- small_oplog = options.small_oplog
- no_journal = options.no_journal
- no_preallocj = options.no_preallocj
+ buildlogger_opts = (options.buildlogger_builder, options.buildlogger_buildnum, options.buildlogger_credentials)
+ if all(buildlogger_opts):
+ os.environ['MONGO_USE_BUILDLOGGER'] = 'true'
+ os.environ['MONGO_BUILDER_NAME'] = options.buildlogger_builder
+ os.environ['MONGO_BUILD_NUMBER'] = options.buildlogger_buildnum
+ os.environ['BUILDLOGGER_CREDENTIALS'] = options.buildlogger_credentials
+ if options.buildlogger_phase:
+ os.environ['MONGO_PHASE'] = options.buildlogger_phase
+ elif any(buildlogger_opts):
+ # some but not all of the required options were sete
+ raise Exception("you must set all of --buildlogger-builder, --buildlogger-buildnum, --buildlogger-credentials")
if options.File:
if options.File == '-':
tests = sys.stdin.readlines()
else:
- with open(options.File) as f:
- tests = f.readlines()
+ f = open(options.File)
+ tests = f.readlines()
tests = [t.rstrip('\n') for t in tests]
+ if options.only_old_fails:
+ run_old_fails()
+ return
+ elif options.reset_old_fails:
+ clear_failfile()
+ return
+
# If we're in suite mode, tests is a list of names of sets of tests.
if options.mode == 'suite':
tests = expand_suites(tests)
elif options.mode == 'files':
tests = [(os.path.abspath(test), True) for test in tests]
+ if options.ignore_files != None :
+ ignore_patt = re.compile( options.ignore_files )
+ tests = filter( lambda x : ignore_patt.search( x[0] ) == None, tests )
+
if not tests:
- raise Exception( "no tests specified" )
+ print "warning: no tests specified"
+ return
+
+ if options.with_cleanbb:
+ dbroot = os.path.join(options.smoke_db_prefix, 'data', 'db')
+ call([utils.find_python(), "buildscripts/cleanbb.py", "--nokill", dbroot])
try:
run_tests(tests)
finally:
+ add_to_failfile(fails, options)
report()
diff --git a/buildscripts/utils.py b/buildscripts/utils.py
index bde2b083612..1f04d7b6af4 100644
--- a/buildscripts/utils.py
+++ b/buildscripts/utils.py
@@ -1,9 +1,14 @@
+import codecs
import re
import socket
import time
import os
+import os.path
+import itertools
+import subprocess
import sys
+import hashlib
# various utilities that are handy
@@ -136,34 +141,84 @@ def didMongodStart( port=27017 , timeout=20 ):
timeout = timeout - 1
return False
-def smoke_python_name():
- # if this script is being run by py2.5 or greater,
- # then we assume that "python" points to a 2.5 or
- # greater python VM. otherwise, explicitly use 2.5
- # which we assume to be installed.
- min_version_tuple = (2, 5)
+def which(executable):
+ if sys.platform == 'win32':
+ paths = os.environ.get('Path', '').split(';')
+ else:
+ paths = os.environ.get('PATH', '').split(':')
+
+ for path in paths:
+ path = os.path.expandvars(path)
+ path = os.path.expanduser(path)
+ path = os.path.abspath(path)
+ executable_path = os.path.join(path, executable)
+ if os.path.exists(executable_path):
+ return executable_path
+
+ return executable
+
+def md5sum( file ):
+ #TODO error handling, etc..
+ return execsys( "md5sum " + file )[0].partition(" ")[0]
+
+def md5string( a_string ):
+ return hashlib.md5(a_string).hexdigest()
+
+def find_python(min_version=(2, 5)):
try:
- if sys.version_info >= min_version_tuple:
+ if sys.version_info >= min_version:
return sys.executable
except AttributeError:
# In case the version of Python is somehow missing sys.version_info or sys.executable.
pass
- import subprocess
version = re.compile(r'[Pp]ython ([\d\.]+)', re.MULTILINE)
- binaries = ['python2.5', 'python2.6', 'python2.7', 'python25', 'python26', 'python27', 'python']
+ binaries = ('python27', 'python2.7', 'python26', 'python2.6', 'python25', 'python2.5', 'python')
for binary in binaries:
try:
- # py-2.4 compatible replacement for shell backticks
out, err = subprocess.Popen([binary, '-V'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
for stream in (out, err):
match = version.search(stream)
if match:
versiontuple = tuple(map(int, match.group(1).split('.')))
- if versiontuple >= min_version_tuple:
- return binary
+ if versiontuple >= min_version:
+ return which(binary)
except:
pass
- # if that all fails, fall back to "python"
- return "python"
+
+ raise Exception('could not find suitable Python (version >= %s)' % '.'.join(str(v) for v in min_version))
+
+def smoke_command(*args):
+ # return a list of arguments that comprises a complete
+ # invocation of smoke.py
+ here = os.path.dirname(__file__)
+ smoke_py = os.path.abspath(os.path.join(here, 'smoke.py'))
+ # the --with-cleanbb argument causes smoke.py to run
+ # buildscripts/cleanbb.py before each test phase; this
+ # prevents us from running out of disk space on slaves
+ return [find_python(), smoke_py, '--with-cleanbb'] + list(args)
+
+def run_smoke_command(*args):
+ # to run a command line script from a scons Alias (or any
+ # Action), the command sequence must be enclosed in a list,
+ # otherwise SCons treats it as a list of dependencies.
+ return [smoke_command(*args)]
+
+# unicode is a pain. some strings cannot be unicode()'d
+# but we want to just preserve the bytes in a human-readable
+# fashion. this codec error handler will substitute the
+# repr() of the offending bytes into the decoded string
+# at the position they occurred
+def replace_with_repr(unicode_error):
+ offender = unicode_error.object[unicode_error.start:unicode_error.end]
+ return (unicode(repr(offender).strip("'").strip('"')), unicode_error.end)
+
+codecs.register_error('repr', replace_with_repr)
+
+def unicode_dammit(string, encoding='utf8'):
+ # convert a string to a unicode, using the Python
+ # representation of non-ascii bytes when necessary
+ #
+ # name inpsired by BeautifulSoup's "UnicodeDammit"
+ return string.decode(encoding, 'repr')