Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure throttle level is always set fixes bug 731325 #459

Merged
merged 1 commit into from Mar 27, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
144 changes: 144 additions & 0 deletions docs/addaservice.rst
Expand Up @@ -189,3 +189,147 @@ use it in the WebApp. If so, have a look at :ref:`ui-chapter`.
You might also want to document it. We are keeping track of all existing
services' documentation in our :ref:`middleware-chapter` page. Please add
yours!

Writing a PostgreSQL middleware unit test
-----------------------------------------

First create your new test file in the appropriate localtion as specified above,
for example socorro/unittest/external/postgresql/test_myservice.py

Next you want to import the following:
::
from socorro.external.postgresql.myservice import MyService
import socorro.unittest.testlib.util as testutil

As this is a PostgreSQL service unit test we also add:
::
from .unittestbase import PostgreSQLTestCase

Next item to add is your setup_module function, below is a barebones version that
would be sufficient for most tests:
::
#------------------------------------------------------------------------------
def setup_module():
testutil.nosePrintModule(__file__)

Next is the setup function in which you create and populate your dummy table(s)
::
#==============================================================================
class TestMyService(PostgreSQLTestCase):

#--------------------------------------------------------------------------
def setUp(self):

super(TestMyService, self).setUp()

cursor = self.connection.cursor()

#Create table
cursor.execute("""
CREATE TABLE product_info
(
product_version_id integer not null,
product_name citext,
version_string citext,
);
""")

# Insert data
cursor.execute("""
INSERT INTO product_info VALUES
(
1,
'%s',
'%s'
);
""" % ("Firefox", "8.0"))

self.connection.commit()

For your test table(s) you can include as many, or as few, columns and rows of data as your tests
will require. Next we add the tearDown function that will clean up after our tests has run, by
dropping tables we created in the setUp function.
::
#--------------------------------------------------------------------------
def tearDown(self):
""" Cleanup the database, delete tables and functions """
cursor = self.connection.cursor()
cursor.execute("""
DROP TABLE product_info;
""")
self.connection.commit()
super(TestProducts, self).tearDown()

Next, we write our actual tests against the dummy data we created in setUp. First step is to create an
instance of the class we are going to test:
::
#--------------------------------------------------------------------------
def test_get(self):
products = Products(config=self.config)

Next we write our first test passing the parameters to our function it expects:
::
#......................................................................
# Test 1: find one exact match for one product and one version
params = {
"versions": "Firefox:8.0"
}

Next we call our function passing the above parameters:
::
res = products.get_versions(**params)

The above will now return a response that we need to test and determine whether it contains what we expect.
In order to do this we create our expected response:
::
res_expected = {
"hits": [
{
"product_version_id": 1,
"product_name": "Firefox",
"version_string": "8.0"
}
],
"total": 1
}

And finally we call the assertEquals function to test whether our response matches our expected response:
::
self.assertEqual(res, res_expected)

Running a PostgreSQL middleware unit test
-----------------------------------------

If you have not already done so, install nose tests. From the commons line run the command:
::
sudo apt-get install python-nose

Once the installation completes change directory to, socorro/unittest/config/ and run the following:
::
cp commonconfig.py.dist commonconfig.py

Now you can open up the file and edit it's contents to match your testing environment. If you are running this in a VM via
Socorro Vagrant, you can leave the content of the file as is. Next cd into socorro/unittest. To run all of the unit tests,
run the following:
::
nosetests

When writing a new test you most likely are more interested in running your own, and just your own, instead of running all
of the unit tests that form part of Socorro. If your test is located in, for example unittest/external/postgresql/test_myservice.py
then you can run your test as follows:
::
nosetests socorro.external.postgresql.test_myservice

Ensuring good style
-------------------

To ensure that the Python code you wrote passes PEP8 you need to run check.py.
To do this your first step is to install it. From the terminal run:
::
pip install -e git://github.com/jbalogh/check.git#egg=check

P.S. You may need to sudo the command above

Once installed, run the following:
::
check.py /path/to/your/file
50 changes: 50 additions & 0 deletions docs/middleware.rst
Expand Up @@ -18,6 +18,7 @@ New-style, documented services
* `extensions/ <#id7>`_
* products/
* `products/builds/ <#products-builds>`_
* `products/versions/ <#products-versions>`_
* report/
* `report/list/ <#list-report>`_
* search/
Expand Down Expand Up @@ -409,6 +410,55 @@ Return an array of objects::
...
]

Products Versions
-----------------

Return information about product(s) and version(s) passed to the service.

API specifications
^^^^^^^^^^^^^^^^^^

+----------------+--------------------------------------------------------------------------------+
| HTTP method | GET |
+----------------+--------------------------------------------------------------------------------+
| URL schema | /products/(.*) |
+----------------+--------------------------------------------------------------------------------+
| Full URL | /products/versions/(versions)/ |
+----------------+--------------------------------------------------------------------------------+
| Example | http://socorro-api/bpapi/products/versions/Firefox:9.0a1/ |
+----------------+--------------------------------------------------------------------------------+

Mandatory parameters
^^^^^^^^^^^^^^^^^^^^

+----------+---------------------------+---------------+----------------------------------------+
| Name | Type of value | Default value | Description |
+==========+===========================+===============+========================================+
| versions | String or list of strings | None | Several product:version strings can |
| | | | be specified, separated by a + symbol. |
+----------+---------------------------+---------------+----------------------------------------+

Return value
^^^^^^^^^^^^

Return an object with an array of results labeled as hits and a total::

{
"hits": [
{
"is_featured": boolean,
"throttle": float,
"end_date": "string",
"start_date": "integer",
"build_type": "string",
"product": "string",
"version": "string"
}
...
],
"total": 1
}

.. ############################################################################
Search API
############################################################################
Expand Down
2 changes: 2 additions & 0 deletions scripts/config/webapiconfig.py.dist
Expand Up @@ -126,6 +126,7 @@ import socorro.middleware.crashes_comments_service as crashes_comments
import socorro.middleware.crash_service as crash_new
import socorro.middleware.extensions_service as extensions
import socorro.middleware.crashes_paireduuid_service as crashes_paireduuid
import socorro.middleware.products_versions_service as products_versions

servicesList = cm.Option()
servicesList.doc = 'a python list of classes to offer as services'
Expand Down Expand Up @@ -154,6 +155,7 @@ servicesList.default = [
crash_new.Crash,
extensions.Extensions,
crashes_paireduuid.CrashesPaireduuid,
products_versions.ProductsVersions,
]

crashBaseUrl = cm.Option()
Expand Down
97 changes: 97 additions & 0 deletions socorro/external/postgresql/products.py
@@ -0,0 +1,97 @@
import logging
import psycopg2

from socorro.external.postgresql.base import add_param_to_dict, PostgreSQLBase
from socorro.lib import datetimeutil, external_common

import socorro.database.database as db

logger = logging.getLogger("webapi")


class MissingOrBadArgumentException(Exception):
pass


class Products(PostgreSQLBase):

def get_versions(self, **kwargs):
""" Return product information for one or more product:version
combinations """
filters = [
("versions", None, ["list", "str"])
]

params = external_common.parse_arguments(filters, kwargs)

if not params.versions or params.versions[0] == '':
raise MissingOrBadArgumentException(
"Mandatory parameter 'versions' missing or empty")

products = []
(params["products_versions"],
products) = self.parse_versions(params["versions"], [])

sql_select = """
SELECT product_name as product,
version_string as version,
start_date,
end_date,
is_featured,
build_type,
throttle::float
FROM product_info WHERE
"""

sql_where = []
versions_list = []
products_list = []
for x in range(0, len(params["products_versions"]), 2):
products_list.append(params["products_versions"][x])
versions_list.append(params["products_versions"][x + 1])

sql_where = ["(product_name = %(product" + str(x) +
")s AND version_string = %(version" + str(x) + ")s)"
for x in range(len(products_list))]

sql_params = {}
sql_params = add_param_to_dict(sql_params, "product", products_list)
sql_params = add_param_to_dict(sql_params, "version", versions_list)

sql_query = " ".join(
("/* socorro.external.postgresql.Products.get_versions */",
sql_select, " OR ".join(sql_where)))

json_result = {
"total": 0,
"hits": []
}

try:
connection = self.database.connection()
cur = connection.cursor()
results = db.execute(cur, sql_query, sql_params)
except psycopg2.Error:
logger.error(
"Failed retrieving products_versions data from PostgreSQL",
exc_info=True)
else:
for product in results:
row = dict(zip((
"product",
"version",
"start_date",
"end_date",
"is_featured",
"build_type",
"throttle"), product))
json_result["hits"].append(row)
row["start_date"] = datetimeutil.date_to_string(
row["start_date"])
row["end_date"] = datetimeutil.date_to_string(
row["end_date"])
json_result["total"] = len(json_result["hits"])

return json_result
finally:
connection.close()
24 changes: 24 additions & 0 deletions socorro/middleware/products_versions_service.py
@@ -0,0 +1,24 @@
import logging

from socorro.middleware.service import DataAPIService

logger = logging.getLogger("webapi")


class ProductsVersions(DataAPIService):

service_name = "products"
uri = "/products/(.*)"

def __init__(self, config):
super(ProductsVersions, self).__init__(config)
logger.debug('ProductsVersions service __init__')

def get(self, *args):
params = self.parse_query_string(args[0])

module = self.get_module(params)

impl = module.Products(config=self.context)

return impl.get_versions(**params)