Skip to content
This repository has been archived by the owner on Aug 22, 2019. It is now read-only.

Commit

Permalink
Add Programs load test
Browse files Browse the repository at this point in the history
Includes a task exercising the Programs API's program list endpoint. Also cleans up some aspects of this repo. ECOM-2627.
  • Loading branch information
Renzo Lucioni committed Oct 27, 2015
1 parent a930a57 commit eb5a384
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
*~
*.DS_Store
*.log
*.egg-info
results/
data/
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ cache:
directories:
- $HOME/.cache/pip
install:
- pip install pip==6.1.0
- pip install -U pip
- pip install -r dev-requirements.txt
script:
- pep8 .
Expand Down
File renamed without changes.
61 changes: 44 additions & 17 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,28 +1,55 @@
This project is the single source for public load tests for edX software component. New tests should be developed here unless there is a very good reason why that cannot be the case. Old tests should be scrubbed and moved here over time.
edX Load Tests |Travis|_
=========================
.. |Travis| image:: https://travis-ci.org/edx/edx-load-tests.svg?branch=master
.. _Travis: https://travis-ci.org/edx/edx-load-tests

This repository is home to public load tests for edX software components. New tests should be developed here. Old tests should be scrubbed and moved here over time.

Installation
------------
Getting Started
---------------

mkvirtualenv edx-load-tests
pip install -r locust/requirements.txt
cd locust/$TEST_DIR
locust --host="http://localhost" -f $csm
If you have not already done so, create and activate a `virtualenv <https://virtualenvwrapper.readthedocs.org/en/latest/>`_. Unless otherwise stated, assume all commands below are executed within said virtualenv.

Layout
------
Next, install load testing requirements.

Each set of tasks should be captured as a top-level locustfile named
for the particular set of endpoints being tested. This toplevel file
can be a flat python file (lms.py) or a directory (csm/). In the case
of a directory, the __init__.py file should have (or import) the Locust
subclass that defines the test.
.. code-block:: bash
$ pip install -r requirements.txt
Start Locust by providing the Locust CLI with a target host and pointing it to the location of your desired locustfile. For example,

.. code-block:: bash
$ locust --host=http://localhost:8009 -f programs
Repository Structure
--------------------

Tests are organized into top-level packages. For examples, see ``csm`` and ``programs``. A module called ``locustfile.py`` is included inside each test package, within which a subclass of the `Locust class <http://docs.locust.io/en/latest/writing-a-locustfile.html#the-locust-class>`_ is defined. This subclass is imported into the test package's ``__init__.py`` to facilitate discovery at runtime.

License
-------

The code in this repository is licensed under version 3 of the AGPL
unless otherwise noted. Please see the `LICENSE`_ file for details.
The code in this repository is licensed under the AGPLv3 unless otherwise noted. Please see `LICENSE.txt <https://github.com/edx/edx-load-tests/blob/master/LICENSE.txt>`_ for details.

How To Contribute
-----------------

Contributions are very welcome.

Please read `How To Contribute <https://github.com/edx/edx-platform/blob/master/CONTRIBUTING.rst>`_ for details.

Even though they were written with ``edx-platform`` in mind, the guidelines
should be followed for Open edX code in general.

Reporting Security Issues
-------------------------

Please do not report security issues in public. Please email security@edx.org.

Mailing List and IRC Channel
----------------------------

.. _LICENSE: https://github.com/edx/edx-load-tests/blob/master/LICENSE
You can discuss this code in the `edx-code Google Group`__ or in the ``#edx-code`` IRC channel on Freenode.

__ https://groups.google.com/forum/#!forum/edx-code
3 changes: 0 additions & 3 deletions dev-requirements.txt

This file was deleted.

58 changes: 58 additions & 0 deletions helpers/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Pair Locust and Slumber to allow easier load testing of REST APIs.
See: http://www.renzolucioni.com/pairing-locust-and-slumber/.
"""
from edx_rest_api_client import exceptions
from edx_rest_api_client.client import EdxRestApiClient
import requests
import slumber


class LocustResource(slumber.Resource):
"""Custom Slumber Resource which takes advantage of Locust's extended HttpSession."""
def _request(self, method, data=None, files=None, params=None):
serializer = self._store['serializer']
url = self.url()

headers = {'accept': serializer.get_content_type()}

if not files:
headers['content-type'] = serializer.get_content_type()
if data is not None:
data = serializer.dumps(data)

# An optional argument that can be used to specify a label to use in Locust's statistics
# instead of the actual URL. Can be used to group requests to the same API endpoint that vary
# only by resource ID included in the URL into a single entry in Locust's statistics. For example,
# requests to
# 'http://localhost:8002/api/v1/resource/1/'
# and
# 'http://localhost:8002/api/v1/resource/2/'
# might be grouped under the name:
# '/api/v1/resource/:id/'
# See: http://docs.locust.io/en/latest/api.html#httpsession-class/.
name = params.pop('name', None)

resp = self._store['session'].request(
method,
url,
data=data,
params=params,
files=files,
headers=headers,
name=name
)

if 400 <= resp.status_code <= 499:
exception_class = exceptions.HttpNotFoundError if resp.status_code == 404 else exceptions.HttpClientError
raise exception_class('Client Error %s: %s' % (resp.status_code, url), response=resp, content=resp.content)
elif 500 <= resp.status_code <= 599:
raise exceptions.HttpServerError('Server Error %s: %s' % (resp.status_code, url), response=resp, content=resp.content)

self._ = resp

return resp


class LocustEdxRestApiClient(EdxRestApiClient):
resource_class = LocustResource
37 changes: 37 additions & 0 deletions programs/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Programs Load Testing
=====================

This directory contains Locust tasks designed to exercise the `edX Programs Service <https://github.com/edx/programs>`_.

Getting Started
---------------

At this point, these load tests only require a running instance of the Programs service.

Configuration
-------------

The load tests rely on configuration which can be specified using environment variables.

==================== ========= =================================
Variable Required? Description
==================== ========= =================================
PROGRAMS_SERVICE_URL No URL root for the Programs service
PROGRAMS_API_URL No URL root for the Programs API
JWT_AUD No JWT audience claim (aud)
JWT_ISS No JWT issuer claim (iss)
JWT_SECRET_KEY Yes Secret key used to sign JWTs
==================== ========= =================================

If you want to use the defaults provided for these variables, make sure that these defaults are configured for your instance of the Programs service.

Running
-------

You can run the Programs load tests from the top level ``edx-load-tests`` directory by executing something like the following:

.. code-block:: bash
$ JWT_SECRET_KEY=replace-me locust --host=http://localhost:8009/ -f programs
There appears to be a bug in Locust preventing tests from accessing hosts using SSL. To get around this, use ``http`` as the protocol in the host URL, not ``https``.
1 change: 1 addition & 0 deletions programs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from programs.locustfile import ProgramsUser
25 changes: 25 additions & 0 deletions programs/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Configuration for Programs load testing."""
import os


# URL CONFIGURATION
PROGRAMS_SERVICE_URL = os.environ.get(
'PROGRAMS_SERVICE_URL',
'http://localhost:8009'
).strip('/')

PROGRAMS_API_URL = os.environ.get(
'PROGRAMS_API_URL',
'{}/api/v1/'.format(PROGRAMS_SERVICE_URL)
)
# END URL CONFIGURATION


# JWT CONFIGURATION
JWT_AUD = os.environ.get('JWT_AUD', 'replace-me')
JWT_ISS = os.environ.get('JWT_ISS', 'http://127.0.0.1:8000/oauth2')
JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY')

if not JWT_SECRET_KEY:
raise RuntimeError('A JWT secret key is required to run Programs load tests.')
# END JWT CONFIGURATION
40 changes: 40 additions & 0 deletions programs/locustfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from datetime import datetime
import uuid

import jwt
from locust import TaskSet, task, HttpLocust

from helpers.api import LocustEdxRestApiClient
from programs.config import PROGRAMS_API_URL, JWT_AUD, JWT_ISS, JWT_SECRET_KEY


class ProgramsTaskSet(TaskSet):
"""Tasks exercising Programs functionality."""
USERNAME_PREFIX = 'load-test-'

@task
def list_programs(self):
api = LocustEdxRestApiClient(PROGRAMS_API_URL, session=self.client, jwt=self._get_token())
api.programs.get()

def _get_token(self):
payload = {
'preferred_username': self.USERNAME_PREFIX + str(uuid.uuid4()),
'iss': JWT_ISS,
'aud': JWT_AUD,
'exp': datetime.utcnow(),
}

return jwt.encode(payload, JWT_SECRET_KEY)


class ProgramsUser(HttpLocust):
"""Representation of an HTTP "user" to be hatched.
Hatched users will be used to attack the system being load tested. This class
defines how long a simulated user should wait between executing tasks, as
well as which TaskSet class should define the user's behavior.
"""
task_set = ProgramsTaskSet
min_wait = 3 * 1000
max_wait = 5 * 1000
8 changes: 1 addition & 7 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
# Packages needed to run any loadtest in this repo

-e git+https://github.com/edx/locust.git@edx#egg=locustio[scipy]
-e .
jupyter
runipy
seaborn
-r requirements/dev.txt
7 changes: 7 additions & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Packages required to run load tests
-e git+https://github.com/edx/locust.git@edx#egg=locustio[scipy]
-e .
edx-rest-api-client==1.2.1
jupyter
runipy
seaborn
4 changes: 4 additions & 0 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Packages required to develop load tests
-r base.txt

pep8==1.6.2

0 comments on commit eb5a384

Please sign in to comment.