Initial Commit
This commit is contained in:
parent
13231ba86a
commit
f4241be7f6
10
AUTHORS.rst
Normal file
10
AUTHORS.rst
Normal 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
277
HACKING.rst
Normal 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
1
MANIFEST.in
Normal file
@ -0,0 +1 @@
|
||||
recursive-include public *
|
0
cdn/__init__.py
Normal file
0
cdn/__init__.py
Normal file
56
cdn/bootstrap.py
Normal file
56
cdn/bootstrap.py
Normal 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
29
cdn/storage/base.py
Normal 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
|
0
cdn/storage/mongodb/__init__.py
Normal file
0
cdn/storage/mongodb/__init__.py
Normal file
15
cdn/storage/mongodb/hosts.py
Normal file
15
cdn/storage/mongodb/hosts.py
Normal 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
21
cdn/tests/__init__.py
Normal 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
25
cdn/tests/config.py
Normal 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
|
20
cdn/tests/test_functional.py
Normal file
20
cdn/tests/test_functional.py
Normal 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
7
cdn/tests/test_units.py
Normal file
@ -0,0 +1,7 @@
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestUnits(TestCase):
|
||||
|
||||
def test_units(self):
|
||||
assert 5 * 5 == 25
|
0
cdn/transport/__init__.py
Normal file
0
cdn/transport/__init__.py
Normal file
31
cdn/transport/base.py
Normal file
31
cdn/transport/base.py
Normal 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
|
0
cdn/transport/falcon/__init__.py
Normal file
0
cdn/transport/falcon/__init__.py
Normal file
35
cdn/transport/falcon/app.py
Normal file
35
cdn/transport/falcon/app.py
Normal 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
|
56
cdn/transport/falcon/driver.py
Normal file
56
cdn/transport/falcon/driver.py
Normal 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()
|
18
cdn/transport/falcon/hosts.py
Normal file
18
cdn/transport/falcon/hosts.py
Normal 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'
|
||||
}
|
||||
]
|
54
cdn/transport/falcon/v1.py
Normal file
54
cdn/transport/falcon/v1.py
Normal 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
|
0
cdn/transport/pecan/__init__.py
Normal file
0
cdn/transport/pecan/__init__.py
Normal file
12
cdn/transport/pecan/app.py
Normal file
12
cdn/transport/pecan/app.py
Normal 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
|
||||
)
|
43
cdn/transport/pecan/driver.py
Normal file
43
cdn/transport/pecan/driver.py
Normal 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()
|
40
cdn/transport/pecan/hosts.py
Normal file
40
cdn/transport/pecan/hosts.py
Normal 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
|
13
cdn/transport/pecan/root.py
Normal file
13
cdn/transport/pecan/root.py
Normal 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
16
cdn/transport/pecan/v1.py
Normal 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
0
cmd/__init__.py
Normal file
31
cmd/server.py
Normal file
31
cmd/server.py
Normal 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
83
common/cli.py
Normal 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
54
etc/cdn.conf
Normal 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
15
requirements.txt
Normal 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
52
setup.cfg
Normal 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
22
setup.py
Normal 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
22
test-requirements.txt
Normal 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
38
tox.ini
Normal 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._
|
Loading…
x
Reference in New Issue
Block a user