diff options
Diffstat (limited to 'scripts/scalability')
9 files changed, 239 insertions, 20 deletions
| diff --git a/scripts/scalability/test_controller/client/Blobs.conf b/scripts/scalability/test_controller/client/Blobs.conf new file mode 100644 index 00000000..d5a2761f --- /dev/null +++ b/scripts/scalability/test_controller/client/Blobs.conf @@ -0,0 +1,34 @@ +[main] +title=Blobs Scalability Tests +description=Upload and download blobs +url=http://giraffe.cdev.bitmask.net:2424/ +size=10 +templates_dir=/tmp/templates + +[test_upload] +description=Upload blobs stress test + +[test_download] +description=Download blobs stress test + +[ftest] +log_to = console file +log_path = logs/blobs-test.log +result_path = results/blobs-test.xml +sleep_time_min = 0 +sleep_time_max = 0 + +[bench] +cycles = 25:50:75:100:125:150:175:200 +duration = 30 +#startup_delay = 0 +#sleep_time = 0 +#cycle_time = 0 +startup_delay = 0.01 +sleep_time = 0.01 +cycle_time = 1 +log_to = console file +log_path = logs/blobs-bench.log +result_path = results/blobs-bench.xml +sleep_time_min = 0 +sleep_time_max = 0 diff --git a/scripts/scalability/test_controller/client/logs/.empty b/scripts/scalability/test_controller/client/logs/.empty new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/scripts/scalability/test_controller/client/logs/.empty diff --git a/scripts/scalability/test_controller/client/makefile b/scripts/scalability/test_controller/client/makefile new file mode 100644 index 00000000..33e67e25 --- /dev/null +++ b/scripts/scalability/test_controller/client/makefile @@ -0,0 +1,19 @@ +bench: upload download + +upload: +	curl -X POST "http://127.0.0.1:7001/blobs?action=delete" +	fl-run-bench -f test_Blobs.py Blobs.test_upload +	fl-build-report --html results/blobs-bench.xml + +download: +	curl -X POST "http://127.0.0.1:7001/blobs?action=create&size=10&amount=5000" +	fl-run-bench -f test_Blobs.py Blobs.test_download +	fl-build-report --html results/blobs-bench.xml + +test_upload: +	curl -X POST "http://127.0.0.1:7001/blobs?action=delete" +	fl-run-bench -c 1 --duration 10 -f test_Blobs.py Blobs.test_upload + +test_download: +	curl -X POST "http://127.0.0.1:7001/blobs?action=create&size=10" +	fl-run-bench -c 1 --duration 10 -f test_Blobs.py Blobs.test_download diff --git a/scripts/scalability/test_controller/client/results/.empty b/scripts/scalability/test_controller/client/results/.empty new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/scripts/scalability/test_controller/client/results/.empty diff --git a/scripts/scalability/test_controller/client/templates.py b/scripts/scalability/test_controller/client/templates.py index dcf51fe1..434eda67 100755 --- a/scripts/scalability/test_controller/client/templates.py +++ b/scripts/scalability/test_controller/client/templates.py @@ -1,10 +1,7 @@  #!/usr/bin/env python -import base64 -import errno  import mock  import os -import random  from argparse import ArgumentParser  from io import BytesIO @@ -14,6 +11,8 @@ from twisted.internet import reactor, defer  from leap.soledad.client._db.blobs import BlobManager +from test_controller.utils import mkdir_p, payload +  DEFAULT_TARGET_DIR = './blob-templates' @@ -33,28 +32,11 @@ def _get_encrypt_function(user, path):      return manager._encrypt_and_upload -def payload(size): -    random.seed(1337)  # same seed to avoid different bench results -    payload_bytes = bytearray(random.getrandbits(8) for _ in xrange(size)) -    # encode as base64 to avoid ascii encode/decode errors -    return base64.b64encode(payload_bytes)[:size]  # remove b64 overhead - -  def _encrypt(path, data):      encrypt = _get_encrypt_function('user-0', path)      return encrypt('blob', BytesIO(data)) -def mkdir_p(path): -    try: -        os.makedirs(path) -    except OSError as exc:  # Python >2.5 -        if exc.errno == errno.EEXIST and os.path.isdir(path): -            pass -        else: -            raise - -  def create_blob_templates(target_dir, amount, size):      mkdir_p(target_dir)      data = payload(size) diff --git a/scripts/scalability/test_controller/client/test_Blobs.py b/scripts/scalability/test_controller/client/test_Blobs.py new file mode 100644 index 00000000..5f31dbc8 --- /dev/null +++ b/scripts/scalability/test_controller/client/test_Blobs.py @@ -0,0 +1,80 @@ +import base64 +import os +import subprocess +import unittest +import urlparse +import threading + +from funkload.FunkLoadTestCase import FunkLoadTestCase +from webunit.utility import Upload + + +SIZE = 100  # KB +DELETE_ON_SETUP = False +DELETE_ON_TEARDOWN = False + + +def _get_auth_header(user_id): +    encoded = base64.b64encode('%s:%s-token' % (user_id, user_id)) +    return 'Token %s' % encoded + + +def _ensure_template(templates_dir, size): +    fname = '%dK-0.blob' % size +    fpath = os.path.join(templates_dir, fname) +    if not os.path.isfile(fpath): +        dirname = os.path.dirname(os.path.realpath(__file__)) +        executable = os.path.join(dirname, 'templates.py') +        code = subprocess.check_call([ +            executable, +            '--amount', '1', +            '--size', str(size), +            templates_dir, +        ]) +        assert code == 0, 'failed creating template' +    return fpath + + +class Blobs(FunkLoadTestCase): + +    next_user_id = 0 +    lock = threading.Lock() + +    def _get_next_user_id(self): +        with Blobs.lock: +            user_id = Blobs.next_user_id +            Blobs.next_user_id += 1 +            Blobs.next_user_id %= 5000 +        return user_id + +    def setUp(self): +        user_id = self._get_next_user_id() +        base_url = self.conf_get('main', 'url') +        self.url = urlparse.urljoin(base_url, 'blobs/%s/0' % user_id) +        self.setHeader('Authorization', _get_auth_header(user_id)) +        templates_dir = self.conf_get('main', 'templates_dir') +        size = self.conf_getInt('main', 'size') +        fpath = _ensure_template(templates_dir, size) +        self.upload = ['file', Upload(fpath)] +        if DELETE_ON_SETUP: +            ret = self.delete(self.url, description='Delete blob on setUp') +            self.assert_(ret.code in [404, 200], 'expected 404 or 200') + +    def tearDown(self): +        if DELETE_ON_TEARDOWN: +            ret = self.delete(self.url, description='Delete blob on tearDown') +            self.assert_(ret.code in [404, 200], 'expected 404 or 200') +        self.clearHeaders() + +    def test_upload(self): +        ret = self.put(self.url, params=[self.upload], +                       description='Upload blob') +        self.assert_(ret.code == 200, "expecting a 200") + +    def test_download(self): +        ret = self.get(self.url, description='Download blob') +        self.assert_(ret.code == 200, "expecting a 200") + + +if __name__ == '__main__': +    unittest.main() diff --git a/scripts/scalability/test_controller/server/blobs.py b/scripts/scalability/test_controller/server/blobs.py new file mode 100644 index 00000000..2a60018d --- /dev/null +++ b/scripts/scalability/test_controller/server/blobs.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +import shutil +import os + +from argparse import ArgumentParser + +from test_controller.utils import mkdir_p, payload + + +def _create_blob(path, data): +    if not os.path.isfile(path): +        with open(path, 'w') as f: +            f.write(data) + + +def create_blobs(target_dir, amount, size): +    data = payload(size * 1000) +    for i in xrange(amount): +        basedir = os.path.join(target_dir, '%d/default/0/0/0' % i) +        mkdir_p(basedir) +        _create_blob(os.path.join(basedir, '0'), data) + + +def delete_blobs(target_dir): +    if not os.path.isdir(target_dir): +        return +    for f in os.listdir(target_dir): +        if f.isdigit(): +            directory = os.path.join(target_dir, f) +            shutil.rmtree(directory) + + +def parse_args(): +    parser = ArgumentParser() +    parser.add_argument( +        'target_dir', +        help='The target directory where templates will be written to.') +    parser.add_argument( +        '--amount', default=1000, type=int, +        help='The amount of users to create blobs to.') +    parser.add_argument( +        '--size', default=1000, type=int, +        help='The size of each template in KB.') +    return parser.parse_args() + + +if __name__ == '__main__': +    args = parse_args() +    create_blobs(args.target_dir, args.amount, args.size * 1000) diff --git a/scripts/scalability/test_controller/server/server.tac b/scripts/scalability/test_controller/server/server.tac index 3d5ea8cc..d5176319 100644 --- a/scripts/scalability/test_controller/server/server.tac +++ b/scripts/scalability/test_controller/server/server.tac @@ -43,9 +43,12 @@ import psutil  from twisted.application import service, internet  from twisted.web import resource, server  from twisted.internet.task import LoopingCall +from twisted.internet.threads import deferToThread  from twisted.logger import Logger  from test_controller.server.user_dbs import ensure_dbs +from test_controller.server.blobs import create_blobs +from test_controller.server.blobs import delete_blobs  DEFAULT_HTTP_PORT = 7001 @@ -224,6 +227,33 @@ class SetupResource(resource.Resource):          request.finish() +class BlobsResource(resource.Resource): + +    def render_POST(self, request): +        action = (request.args.get('action') or ['create']).pop() +        amount = int((request.args.get('amount') or [1000]).pop()) +        size = int((request.args.get('size') or [1000]).pop()) +        if action == 'create': +            d = deferToThread(create_blobs, '/tmp/soledad-server/blobs', +                              amount, size) +        elif action == 'delete': +            d = deferToThread(delete_blobs, '/tmp/soledad-server/blobs') +        d.addCallback(self._success, request) +        d.addErrback(self._error, request) +        return server.NOT_DONE_YET + +    def _success(self, _, request): +        request.write(SUCCESS) +        request.finish() + +    def _error(self, e, request): +        message = e.getErrorMessage() if e.getErrorMessage() else repr(e) +        logger.error('Error processing request: %s' % message) +        request.setResponseCode(500) +        request.write(json.dumps({'error': str(e)})) +        request.finish() + +  class Root(resource.Resource):      def __init__(self): @@ -231,6 +261,7 @@ class Root(resource.Resource):          self.putChild('mem', MonitorResource(MemoryWatcher))          self.putChild('cpu', MonitorResource(CpuWatcher))          self.putChild('setup', SetupResource()) +        self.putChild('blobs', BlobsResource())  application = service.Application("Resource Monitor") diff --git a/scripts/scalability/test_controller/utils.py b/scripts/scalability/test_controller/utils.py new file mode 100644 index 00000000..ebe55571 --- /dev/null +++ b/scripts/scalability/test_controller/utils.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +import base64 +import errno +import os +import random + + +def payload(size): +    random.seed(1337)  # same seed to avoid different bench results +    payload_bytes = bytearray(random.getrandbits(8) for _ in xrange(size)) +    # encode as base64 to avoid ascii encode/decode errors +    return base64.b64encode(payload_bytes)[:size]  # remove b64 overhead + + +def mkdir_p(path): +    try: +        os.makedirs(path) +    except OSError as exc:  # Python >2.5 +        if exc.errno == errno.EEXIST and os.path.isdir(path): +            pass +        else: +            raise | 
