Initial Commit

This commit is contained in:
amitgandhinz 2014-02-12 11:33:37 -05:00
parent 13231ba86a
commit f4241be7f6
34 changed files with 1096 additions and 0 deletions

10
AUTHORS.rst Normal file
View File

@ -0,0 +1,10 @@
Maintainer
----------
Original Authors
----------------
Amit Gandhi (amit.gandhi@rackspace.com)
See also AUTHORS for a complete list of contributors.

277
HACKING.rst Normal file
View File

@ -0,0 +1,277 @@
CDN Style Commandments
==========================
- Step 1: Read http://www.python.org/dev/peps/pep-0008/
- Step 2: Read http://www.python.org/dev/peps/pep-0008/ again
- Step 3: Read on
General
-------
- Optimize for readability; whitespace is your friend.
- Put two newlines between top-level code (funcs, classes, etc.)
- Put one newline between methods in classes and anywhere else.
- Use blank lines to group related logic.
- Never write ``except:`` (use ``except Exception:`` instead, at
the very least).
- All classes must inherit from ``object`` (explicitly).
- Use single-quotes for strings unless the string contains a
single-quote.
- Use the double-quote character for blockquotes (``"""``, not ``'''``)
- USE_ALL_CAPS_FOR_GLOBAL_CONSTANTS
Comments
--------
- In general use comments as "memory pegs" for those coming after you up
the trail.
- Guide the reader though long functions with a comments introducing
different sections of the code.
- Choose clean, descriptive names for functions and variables to make
them self-documenting.
- Include your name with TODOs as in ``# TODO(termie): blah blah...``.
- Add ``# NOTE(termie): blah blah...`` comments to clarify your intent, or
to explain a tricky algorithm, when it isn't obvious from just reading
the code.
Identifiers
-----------
- Do not give anything the same name as a built-in or reserved word.
- Don't use single characters in identifiers except in trivial loop variables and mathematical algorithms.
- Avoid abbreviations, especially if they are ambiguous or their meaning would not be immediately clear to the casual reader or newcomer.
Wrapping
--------
Wrap long lines by using Python's implied line continuation inside
parentheses, brackets and braces. Make sure to indent the continued
line appropriately. The preferred place to break around a binary
operator is after the operator, not before it.
Example::
class Rectangle(Blob):
def __init__(self, width, height,
color='black', emphasis=None, highlight=0):
# More indentation included to distinguish this from the rest.
if (width == 0 and height == 0 and
color == 'red' and emphasis == 'strong' or
highlight > 100):
raise ValueError('sorry, you lose')
if width == 0 and height == 0 and (color == 'red' or
emphasis is None):
raise ValueError("I don't think so -- values are {0}, {1}".format(
width, height))
msg = ('this is a very long string that goes on and on and on and'
'on and on and on...')
super(Rectangle, self).__init__(width, height,
color, emphasis, highlight)
Imports
-------
- Only modules may be imported
- Do not make relative imports
- Order your imports by the full module path
- Classes and functions may be hoisted into a package namespace, via __init__ files, with some discretion.
- Organize your imports according to the template given below
Template::
{{stdlib imports in human alphabetical order}}
\n
{{third-party lib imports in human alphabetical order}}
\n
{{marconi imports in human alphabetical order}}
\n
\n
{{begin your code}}
Human Alphabetical Order Examples
---------------------------------
Example::
import logging
import time
import unittest
import eventlet
import marconi.common
from marconi import test
import marconi.queues.transport
More Import Examples
--------------------
**INCORRECT** ::
import marconi.queues.transport.wsgi as wsgi
**CORRECT** ::
from marconi.queues.transport import wsgi
Docstrings
----------
Docstrings are required for all functions and methods.
Docstrings should ONLY use triple-double-quotes (``"""``)
Single-line docstrings should NEVER have extraneous whitespace
between enclosing triple-double-quotes.
**INCORRECT** ::
""" There is some whitespace between the enclosing quotes :( """
**CORRECT** ::
"""There is no whitespace between the enclosing quotes :)"""
Docstrings should document default values for named arguments
if they're not None
Docstrings that span more than one line should look like this:
Example::
"""Single-line summary, right after the opening triple-double-quote.
If you are going to describe parameters and return values, use Sphinx; the
appropriate syntax is as follows.
:param foo: the foo parameter
:param bar: (Default True) the bar parameter
:param foo_long_bar: the foo parameter description is very
long so we have to split it in multiple lines in order to
keey things ordered
:returns: return_type -- description of the return value
:returns: description of the return value
:raises: AttributeError, KeyError
"""
**DO NOT** leave an extra newline before the closing triple-double-quote.
Dictionaries/Lists
------------------
If a dictionary (dict) or list object is longer than 80 characters, its items
should be split with newlines. Embedded iterables should have their items
indented. Additionally, the last item in the dictionary should have a trailing
comma. This increases readability and simplifies future diffs.
Example::
my_dictionary = {
"image": {
"name": "Just a Snapshot",
"size": 2749573,
"properties": {
"user_id": 12,
"arch": "x86_64",
},
"things": [
"thing_one",
"thing_two",
],
"status": "ACTIVE",
},
}
Calling Methods
---------------
Calls to methods 80 characters or longer should format each argument with
newlines. This is not a requirement, but a guideline::
unnecessarily_long_function_name('string one',
'string two',
kwarg1=constants.ACTIVE,
kwarg2=['a', 'b', 'c'])
Rather than constructing parameters inline, it is better to break things up::
list_of_strings = [
'what_a_long_string',
'not as long',
]
dict_of_numbers = {
'one': 1,
'two': 2,
'twenty four': 24,
}
object_one.call_a_method('string three',
'string four',
kwarg1=list_of_strings,
kwarg2=dict_of_numbers)
Internationalization (i18n) Strings
-----------------------------------
In order to support multiple languages, we have a mechanism to support
automatic translations of exception and log strings.
Example::
msg = _("An error occurred")
raise HTTPBadRequest(explanation=msg)
If you have a variable to place within the string, first internationalize the
template string then do the replacement.
Example::
msg = _("Missing parameter: {0}").format("flavor",)
LOG.error(msg)
If you have multiple variables to place in the string, use keyword parameters.
This helps our translators reorder parameters when needed.
Example::
msg = _("The server with id {s_id} has no key {m_key}")
LOG.error(msg.format(s_id=1234", m_key=imageId"))
Creating Unit Tests
-------------------
For every any change, unit tests should be created that both test and
(implicitly) document the usage of said feature. If submitting a patch for a
bug that had no unit test, a new passing unit test should be added. If a
submitted bug fix does have a unit test, be sure to add a new one that fails
without the patch and passes with the patch.
NOTE: 100% coverage is required
openstack-common
----------------
A number of modules from openstack-common are imported into the project.
These modules are "incubating" in openstack-common and are kept in sync
with the help of openstack-common's update.py script. See:
http://wiki.openstack.org/CommonLibrary#Incubation
The copy of the code should never be directly modified here. Please
always update openstack-common first and then run the script to copy
the changes across.
Logging
-------
Use __name__ as the name of your logger and name your module-level logger
objects 'LOG'::
LOG = logging.getLogger(__name__)

1
MANIFEST.in Normal file
View File

@ -0,0 +1 @@
recursive-include public *

0
cdn/__init__.py Normal file
View File

56
cdn/bootstrap.py Normal file
View File

@ -0,0 +1,56 @@
from __future__ import print_function
from stevedore import driver
class Bootstrap(object):
"""Defines the CDN bootstrapper.
The bootstrap loads up drivers per a given configuration, and
manages their lifetimes.
"""
def __init__(self, conf):
self.conf = conf
def storage(self):
print((u'Loading storage driver'))
# create the driver manager to load the appropriate drivers
storage_type = 'cdn.storage'
# TODO(amitgandhinz): load this from config
storage_name = 'mongodb'
args = [self.conf]
try:
mgr = driver.DriverManager(namespace=storage_type,
name=storage_name,
invoke_on_load=True,
invoke_args=args)
return mgr.driver
except RuntimeError as exc:
print(exc)
def transport(self):
# create the driver manager to load the appropriate drivers
transport_type = 'cdn.transport'
# TODO(amitgandhinz): load this from config
transport_name = 'falcon'
args = [self.conf]
print((u'Loading transport driver: %s'), transport_name)
try:
mgr = driver.DriverManager(namespace=transport_type,
name=transport_name,
invoke_on_load=True,
invoke_args=args)
return mgr.driver
except RuntimeError as exc:
print(exc)
def run(self):
self.transport.listen()

29
cdn/storage/base.py Normal file
View File

@ -0,0 +1,29 @@
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class HostBase(object):
"""This class is responsible for managing hostnames.
Hostname operations include CRUD, etc.
"""
__metaclass__ = abc.ABCMeta
def __init__(self):
pass
@abc.abstractmethod
def list(self, project=None, marker=None,
limit=None, detailed=False):
"""Base method for listing hostnames.
:param project: Project id
:param marker: The last host name
:param limit: (Default 10, configurable) Max number
hostnames to return.
:param detailed: Whether metadata is included
:returns: An iterator giving a sequence of hostnames
and the marker of the next page.
"""
raise NotImplementedError

View File

View File

@ -0,0 +1,15 @@
# stevedore/example/simple.py
from storage import base
class HostController(base.HostBase):
def list(self, project=None, marker=None,
limit=None, detailed=False):
print "list"
def create(self):
print "create"
def delete(self):
print "delete"

21
cdn/tests/__init__.py Normal file
View File

@ -0,0 +1,21 @@
import os
from pecan import set_config
from pecan.testing import load_test_app
from unittest import TestCase
__all__ = ['FunctionalTest']
class FunctionalTest(TestCase):
"""Used for functional tests where you need to test your
literal application and its integration with the framework.
"""
def setUp(self):
self.app = load_test_app(os.path.join(
os.path.dirname(__file__),
'config.py'
))
def tearDown(self):
set_config({}, overwrite=True)

25
cdn/tests/config.py Normal file
View File

@ -0,0 +1,25 @@
# Server Specific Configurations
server = {
'port': '8080',
'host': '0.0.0.0'
}
# Pecan Application Configurations
app = {
'root': 'cdn.controllers.root.RootController',
'modules': ['cdn'],
'static_root': '%(confdir)s/../../public',
'template_path': '%(confdir)s/../templates',
'debug': True,
'errors': {
'404': '/error/404',
'__force_dict__': True
}
}
# Custom Configurations must be in Python dictionary format::
#
# foo = {'bar':'baz'}
#
# All configurations are accessible at::
# pecan.conf

View File

@ -0,0 +1,20 @@
from cdn.tests import FunctionalTest
class TestRootController(FunctionalTest):
def test_get(self):
response = self.app.get('/')
assert response.status_int == 200
def test_search(self):
response = self.app.post('/', params={'q': 'RestController'})
assert response.status_int == 302
assert response.headers['Location'] == (
'http://pecan.readthedocs.org/en/latest/search.html'
'?q=RestController'
)
def test_get_not_found(self):
response = self.app.get('/a/bogus/url', expect_errors=True)
assert response.status_int == 404

7
cdn/tests/test_units.py Normal file
View File

@ -0,0 +1,7 @@
from unittest import TestCase
class TestUnits(TestCase):
def test_units(self):
assert 5 * 5 == 25

View File

31
cdn/transport/base.py Normal file
View File

@ -0,0 +1,31 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class DriverBase(object):
"""Base class for Transport Drivers to document the expected interface.
"""
def __init__(self, conf):
self._conf = conf
@abc.abstractmethod
def listen():
"""Start listening for client requests (self-hosting mode)."""
raise NotImplementedError

View File

View File

@ -0,0 +1,35 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""WSGI App for WSGI Containers
This app should be used by external WSGI
containers. For example:
$ gunicorn cdn.transport.falcon.app:app
NOTE: As for external containers, it is necessary
to put config files in the standard paths. There's
no common way to specify / pass configuration files
to the WSGI app when it is called from other apps.
"""
from cdn import bootstrap
from oslo.config import cfg
conf = cfg.CONF
conf(project='cdn', prog='cdn', args=[])
app = bootstrap.Bootstrap(conf).transport.app

View File

@ -0,0 +1,56 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import falcon
import six
from cdn import transport
from cdn.transport import DriverBase
from wsgiref import simple_server
from hosts import HostsResource
from v1 import V1Resource
@six.add_metaclass(abc.ABCMeta)
class Driver(transport.DriverBase):
def __init__(self, conf):
super(DriverBase, self).__init__()
self.app = None
self._init_routes()
def _init_routes(self):
"""Initialize hooks and URI routes to resources."""
self.app = falcon.API()
version_path = "/v1"
self.app.add_route(version_path + '/', V1Resource)
self.app.add_route(version_path + '/hosts', HostsResource)
def listen(self):
"""Self-host using 'bind' and 'port' from the WSGI config group."""
bind = '127.0.0.1'
port = '8080'
msgtmpl = (u'Serving on host %(bind)s:%(port)s')
print(msgtmpl)
httpd = simple_server.make_server(bind=bind,
port=port,
app=self.app)
httpd.serve_forever()

View File

@ -0,0 +1,18 @@
import falcon
class HostsResource:
def on_get(self, req, resp):
"""Handles GET requests
"""
resp.status = falcon.HTTP_200 # This is the default status
resp.body = [
{
'hostname': 'www.mywebsite.com',
'description': 'My Sample Website'
},
{
'hostname': 'www.myotherwebsite.com',
'description': 'My Other Website'
}
]

View File

@ -0,0 +1,54 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
#
# See the License for the specific language governing permissions and
# limitations under the License.
import json
# NOTE(amitgandhinz): http://tools.ietf.org/html/draft-nottingham-json-home-03
JSON_HOME = {
'resources': {
#------------------------------------------------------------------
# HOSTS
#------------------------------------------------------------------
'rel/cdn': {
'href-template': '/v1/hostnames{?marker,limit,detailed}',
'href-vars': {
'marker': 'param/marker',
'limit': 'param/hostname_limit'
},
'hints': {
'allow': ['GET'],
'formats': {
'application/json': {},
},
},
}
}
}
class V1Resource(object):
def __init__(self):
document = json.dumps(JSON_HOME, ensure_ascii=False, indent=4)
self.document_utf8 = document.encode('utf-8')
def on_get(self, req, resp, project_id):
resp.data = self.document_utf8
resp.content_type = 'application/json-home'
resp.cache_control = ['max-age=86400']
# status defaults to 200

View File

View File

@ -0,0 +1,12 @@
from pecan import make_app
def setup_app(config):
app_conf = dict(config.app)
return make_app(
app_conf.pop('root'),
logging=getattr(config, 'logging', {}),
**app_conf
)

View File

@ -0,0 +1,43 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import six
from cdn import transport
from cdn.transport import DriverBase
from wsgiref import simple_server
@six.add_metaclass(abc.ABCMeta)
class Driver(transport.DriverBase):
def __init__(self, conf):
super(DriverBase, self).__init__()
self.app = None
def listen(self):
"""Self-host using 'bind' and 'port' from the WSGI config group."""
bind = '127.0.0.1'
port = '8080'
msgtmpl = (u'Serving on host %(bind)s:%(port)s')
print(msgtmpl)
httpd = simple_server.make_server(bind=bind,
port=port,
app=self.app)
httpd.serve_forever()

View File

@ -0,0 +1,40 @@
from pecan import expose, response
from pecan.rest import RestController
class HostsController(RestController):
@expose('json')
def get_all(self):
'''return the list of hostnames
'''
return dict(
hostname='www.sample.com'
)
@expose('json')
def get(self, id):
'''return the configuration of the hostname
'''
return dict(
hostname=id,
description='My Sample Website'
)
@expose('json')
def put(self, id):
'''add the hostname
'''
response.status = 201
return dict(
hostname=id,
description='My Sample Website'
)
@expose('json')
def delete(self, id):
'''delete the hostname
'''
response.status = 204
return None

View File

@ -0,0 +1,13 @@
from pecan import expose
from v1 import HomeController
class RootController(object):
v1 = HomeController()
@expose('json')
def notfound(self):
'''return the generic 404 response
'''
return dict(status=404, message="Not Found")

16
cdn/transport/pecan/v1.py Normal file
View File

@ -0,0 +1,16 @@
from hosts import HostsController
from pecan import expose
from pecan.rest import RestController
class HomeController(RestController):
hosts = HostsController()
@expose('json')
def get_all(self):
'''return the HOME document for the API
'''
return dict(
version='1.0'
)

0
cmd/__init__.py Normal file
View File

31
cmd/server.py Normal file
View File

@ -0,0 +1,31 @@
# Copyright (c) 2013 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo.config import cfg
from cdn.common import cli
from cdn.cdn import bootstrap
@cli.runnable
def run():
# TODO(kgriffs): For now, we have to use the global config
# to pick up common options from openstack.common.log, since
# that module uses the global CONF instance exclusively.
conf = cfg.CONF
conf(project='cdn', prog='cdn')
server = bootstrap.Bootstrap(conf)
server.run()

83
common/cli.py Normal file
View File

@ -0,0 +1,83 @@
# Copyright (c) 2013 Rackspace Hosting, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
#
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import atexit
import functools
import os
import sys
import termios
from marconi.openstack.common.gettextutils import _
from marconi.openstack.common import log as logging
LOG = logging.getLogger(__name__)
def _fail(returncode, ex):
"""Handles terminal errors.
:param returncode: process return code to pass to sys.exit
:param ex: the error that occurred
"""
print(ex, file=sys.stderr)
LOG.exception(ex)
sys.exit(returncode)
def _enable_echo(enable):
"""Enables or disables terminal echo.
:param enable: pass True to enable echo, False to disable
"""
if not os.isatty(sys.stdin.fileno()):
# if we are not running in an interactive shell we will get
# termios.error: (25, 'Inappropriate ioctl for device')
return
fd = sys.stdin.fileno()
new_attr = termios.tcgetattr(fd)
if enable:
new_attr[3] |= termios.ECHO
else:
new_attr[3] &= ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, new_attr)
def runnable(func):
"""Entry point wrapper.
Note: This call blocks until the process is killed
or interrupted.
"""
@functools.wraps(func)
def _wrapper():
atexit.register(_enable_echo, True)
_enable_echo(False)
try:
logging.setup('marconi')
func()
except KeyboardInterrupt:
LOG.info(_(u'Terminating'))
except Exception as ex:
_fail(1, ex)
return _wrapper

54
etc/cdn.conf Normal file
View File

@ -0,0 +1,54 @@
# By default, this should live in one of:
# ~/.cdn/cdn.conf
# /etc/cdn/cdn.conf
[DEFAULT]
# Show more verbose log output (sets INFO log level output)
;verbose = False
# Show debugging output in logs (sets DEBUG log level output)
;debug = False
# Log to this file
log_file = /var/log/cdn/cdn.log
;auth_strategy =
# Set to True to enable sharding across multiple storage backends
;sharding = False
# Set to True to activate endpoints to manage the shard registry
;admin_mode = False
# ================= Syslog Options ============================
# Send logs to syslog (/dev/log) instead of to file specified
# by `log_file`
;use_syslog = False
# Facility to use. If unset defaults to LOG_USER.
;syslog_log_facility = LOG_LOCAL0
# ================= Driver Options ============================
[drivers]
# Transport driver module (e.g., wsgi, zmq)
transport = falcon
# Storage driver module (e.g., mongodb, sqlite)
storage = mongodb
[drivers:transport:falcon]
;bind = 0.0.0.0
;port = 8888
;[drivers:transport:pecan]
;bind = 0.0.0.0
;port = 8888
[drivers:storage:mongodb]
uri = mongodb://db1.example.net,db2.example.net:2500/?replicaSet=test&ssl=true&w=majority
database = cdn

15
requirements.txt Normal file
View File

@ -0,0 +1,15 @@
pbr>=0.5.21,<1.0
Babel>=1.3
netaddr>=0.7.6
falcon>=0.1.6,<0.1.7
jsonschema>=1.3.0,!=1.4.0
iso8601>=0.1.8
msgpack-python
pymongo>=2.4
python-keystoneclient>=0.4.1
python-memcached
WebOb>=1.2.3,<1.3
stevedore>=0.10
six>=1.4.1
oslo.config>=1.2.0

52
setup.cfg Normal file
View File

@ -0,0 +1,52 @@
[metadata]
name = cdn
version = 2014.2
summary = CDN Service
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 2.6
[files]
packages =
cdn
[entry_points]
console_scripts =
cdn-server = cdn.cmd.server:run
cdn.transport =
falcon = cdn.transport.falcon.driver:Driver
[nosetests]
where=tests
verbosity=2
with-doctest = true
cover-package = cdn
cover-html = true
cover-erase = true
cover-inclusive = true
; Disabled: Causes a bug in testtools to manifest.
; Trigger: self.assertX(condition), where condition == False.
;
; In "testtools/testresult/real.py" the traceback is set to
; None in _details_to_exc_info(), but the inspect_traceback()
; method in nose/inspector.py requires a traceback-like object.
;
; detailed-errors = 1

22
setup.py Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
setuptools.setup(
setup_requires=['pbr'],
pbr=True)

22
test-requirements.txt Normal file
View File

@ -0,0 +1,22 @@
# Metrics and style
hacking>=0.5.6,<0.8
# Packaging
mock>=1.0
# Unit testing
ddt>=0.4.0
discover
fixtures>=0.3.14
httpretty>=0.6.3
python-subunit
testrepository>=0.0.17
testtools>=0.9.32
# Functional Tests
requests>=1.1
# Test runner
nose
nose-exclude
openstack.nose_plugin>=0.7

38
tox.ini Normal file
View File

@ -0,0 +1,38 @@
[tox]
minversion = 1.6
envlist = py26,py27,py33,pypy,pep8
skipsdist = True
[testenv]
usedevelop = True
# Customize pip command, add -U to force updates.
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
NOSE_WITH_OPENSTACK=1
NOSE_OPENSTACK_COLOR=1
NOSE_OPENSTACK_RED=0.05
NOSE_OPENSTACK_YELLOW=0.025
NOSE_OPENSTACK_SHOW_ELAPSED=1
NOSE_OPENSTACK_STDOUT=1
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = nosetests {posargs}
[tox:jenkins]
downloadcache = ~/cache/pip
[testenv:pep8]
commands = flake8
[testenv:cover]
setenv = NOSE_WITH_COVERAGE=1
[testenv:venv]
commands = {posargs}
[flake8]
builtins = __MARCONI_SETUP__
exclude = .venv*,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*.egg,.update-venv
[hacking]
import_exceptions = marconi.openstack.common.gettextutils._