[z3-checkins] r37889 - in z3/deliverance/DeliveranceVHoster/trunk: dvhoster tests
ianb at codespeak.net
ianb at codespeak.net
Sun Feb 4 04:30:19 CET 2007
Author: ianb
Date: Sun Feb 4 04:30:14 2007
New Revision: 37889
Modified:
z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py
z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py
z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py
z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py
z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py
Log:
Make the functional tests pass, w00t
Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py
==============================================================================
--- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py (original)
+++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py Sun Feb 4 04:30:14 2007
@@ -51,20 +51,93 @@
print 'Creating data directory %s' % self.dir
os.makedirs(self.dir)
- def domain(self, domain_name):
+ def alias_fn(self, alias):
+ return os.path.join(self.dir, alias+'-alias.txt')
+
+ def domain(self, domain_name, aliases=()):
+ """
+ Return the domain object for the given domain_name. The
+ domain name given may not be the canonical domain.
+ """
domain_name = self.normalize(domain_name)
+ alias_fn = self.alias_fn(domain_name)
+ if os.path.exists(alias_fn):
+ f = open(alias_fn, 'rb')
+ alias = f.read()
+ f.close()
+ aliases = list(aliases)
+ if alias in aliases:
+ raise ValueError(
+ "Infinite alias loop (found: %s -> %s)"
+ % (aliases, alias))
+ aliases.append(alias)
+ return self.domain(alias, aliases=aliases)
dir = os.path.join(self.dir, domain_name)
if not os.path.exists(dir):
os.mkdir(dir)
return DomainDataProvider(self, domain_name, dir)
def normalize(self, domain_name):
+ """
+ Normalize a domain name (lower case, no funny characters).
+ """
domain_name = domain_name.strip().lower()
if not domain_re.search(domain_name):
raise ValueError(
'Bad domain name: %r' % domain_name)
return domain_name
+ def add_alias(self, alias, domain):
+ """
+ Add an alias domain ``alias`` which points to ``domain``.
+ There must not be an existing alias.
+ """
+ alias = self.normalize(alias)
+ domain = self.normalize(domain)
+ fn = self.alias_fn(alias)
+ assert not os.path.exists(fn), (
+ "Alias file already exists (%r)" % fn)
+ f = open(fn, 'wb')
+ f.write(domain)
+ f.close()
+
+ def remove_alias(self, alias, domain=None):
+ """
+ Remove the alias for the domain ``alias``. ``domain`` (if
+ given) is what the alias should currently be pointing to (we
+ won't remove the alias then if it points elsewhere).
+ """
+ alias = self.normalize(alias)
+ if domain:
+ domain = self.normalize(domain)
+ fn = self.alias_fn(alias)
+ if not os.path.exists(fn):
+ raise ValueError(
+ "Alias does not exist (not file %r)" % fn)
+ if domain:
+ f = open(fn, 'rb')
+ existing = f.read().strip()
+ f.close()
+ if existing != domain:
+ raise ValueError(
+ "Tried to remove alias from %s->%s, but %s actually "
+ "points to %s" % (alias, domain, alias, existing))
+ os.unlink(fn)
+
+ def rename_domain(self, old_domain_name, new_domain_name):
+ """
+ Rename the old domain to the new domain, returning the new domain
+ object.
+ """
+ old_domain_name = self.normalize(old_domain_name)
+ new_domain_name = self.normalize(new_domain_name)
+ old_dir = os.path.join(self.dir, old_domain_name)
+ new_dir = os.path.join(self.dir, new_domain_name)
+ assert os.path.exists(old_dir)
+ assert not os.path.exists(new_dir)
+ os.rename(old_dir, new_dir)
+ return self.domain(new_domain_name)
+
class DomainDataProvider(object):
def __init__(self, provider, domain_name, base_dir):
@@ -95,7 +168,7 @@
@property
def initialized(self):
- return hasattr(self, 'remote')
+ return hasattr(self, 'remote_uris')
@property
def rule_dir(self):
@@ -105,11 +178,29 @@
def static_dir(self):
return os.path.join(self.base_dir, 'static')
- remote = persist.file_property('remote.txt')
+ remote_uris = descriptors.json_converter(
+ persist.file_property('remote_uris.txt'))
theme_uri = persist.file_property('theme_uri.txt')
theme_id = persist.file_property('theme_id.txt')
rule_ids = persist.file_property('rule_ids.txt')
- aliases = descriptors.line_converter(persist.file_property('aliases.txt'))
+ redirects = descriptors.json_converter(
+ persist.file_property('redirects.txt', default=()))
+
+ def aliases__rename(self, old_aliases, new_aliases):
+ dropped = list(old_aliases)
+ added = list(new_aliases)
+ for new_alias in new_aliases:
+ if new_alias in dropped:
+ dropped.remove(new_alias)
+ added.remove(new_alias)
+ for alias in dropped:
+ self.provider.remove_alias(alias, self.domain)
+ for alias in added:
+ self.provider.add_alias(alias, self.domain)
+
+ aliases = descriptors.watcher(
+ descriptors.line_converter(persist.file_property('aliases.txt', default='')),
+ after_watcher=aliases__rename)
def set_rule_file(self, filename, content):
filename = os.path.join(self.rule_dir, filename)
@@ -119,11 +210,16 @@
def domain__get(self):
return self._domain_name
-
- def domain__set(self, value):
- new_obj = self.provider.rename_domain(self.domain, value)
+
+ def domain__set(self, new_domain):
+ aliases = self.aliases
+ old_domain = self.domain
+ new_obj = self.provider.rename_domain(self.domain, new_domain)
# Clone new object as self:
self.__dict__.update(new_obj.__dict__)
+ for alias in aliases:
+ self.provider.remove_alias(alias, domain=old_domain)
+ self.provider.add_alias(alias, new_domain)
domain = property(domain__get, domain__set)
@@ -133,6 +229,7 @@
class ListDictValidator(validators.FancyValidator):
required_keys = ()
optional_keys = ()
+ trail_slash_keys = ()
def validate_python(self, value, state=None):
# Should be like [{'path': path, 'remote_uri': uri, 'comment': str}]
@@ -158,14 +255,25 @@
except AssertionError, e:
raise validators.Invalid(str(e), value, state)
+ def _to_python(self, value, state=None):
+ for d in value:
+ for key in self.trail_slash_keys:
+ if key not in d:
+ continue
+ if not d[key].endswith('/'):
+ d[key] += '/'
+ return value
+
class RemoteURIValidator(ListDictValidator):
required_keys = ('path', 'remote_uri')
optional_keys = ('comment', )
+ trail_slash_keys = ('path', 'remote_uri')
class RewriteValidator(ListDictValidator):
required_keys = ('rewrite', )
# @@: Really we should require one of path or prefix
optional_keys = ('path', 'prefix', 'comment')
+ trail_slash_keys = ('prefix', 'rewrite')
class ProviderApp(server.ApplicationWrapper):
@@ -190,4 +298,4 @@
def static(self):
if not os.path.exists(self.static_dir):
os.makedirs(self.static_dir)
- return lildav.LilDAV(self.rule_dir)
+ return lildav.LilDAV(self.static_dir)
Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py
==============================================================================
--- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py (original)
+++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py Sun Feb 4 04:30:14 2007
@@ -17,7 +17,17 @@
if not current_environ.get('dvhoster.has_errors'):
current_environ['dvhoster.has_errors'] = True
domain_info = current_environ['dvhoster.domain_info']
- remote = domain_info.remote
+ remote_uris = domain_info.remote_uris
+ path_info = current_environ['PATH_INFO']
+ for remote_info in remote_uris:
+ if (path_info.startswith(remote_info['path'])
+ or not path_info and remote_info['path'] == '/'):
+ remote = remote_info['remote_uri']
+ break
+ else:
+ assert 0, (
+ "Nothing in remote_uris (%r) matches PATH_INFO=%r"
+ % (remote_uris, path_info))
remote += current_environ.get('PATH_INFO', '')
if current_environ.get('QUERY_STRING'):
remote += '?' + current_environ['QUERY_STRING']
Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py
==============================================================================
--- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py (original)
+++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py Sun Feb 4 04:30:14 2007
@@ -1,5 +1,6 @@
import posixpath
import os
+import urlparse
from paste.request import path_info_pop, construct_url
from paste import httpexceptions
from paste.urlparser import StaticURLParser
@@ -32,6 +33,7 @@
domain = environ['HTTP_HOST']
if ':' in domain:
domain = domain.split(':', 1)[0]
+ domain = self.provider.normalize(domain)
domain_info = self.provider.domain(domain)
environ['dvhoster.domain_info'] = domain_info
environ['dvhoster.base_url'] = construct_url(
@@ -42,12 +44,27 @@
path_info_pop(environ)
subapp = ProviderApp(domain_info)
return subapp(environ, start_response)
+
+ if domain_info.domain != domain:
+ # We got an alias
+ assert domain in domain_info.aliases, (
+ "Domain %r not found in aliases %r"
+ % (domain, domain_info.aliases))
+ new_environ = environ.copy()
+ new_environ['HTTP_HOST'] = domain_info.domain
+ new_url = construct_url(new_environ)
+ exc = httpexceptions.HTTPMovedPermanently(
+ headers=[('Location', new_url)],
+ comment='Redirecting to canonical domain')
+ return exc(environ, start_response)
+
if path_info.startswith('/_rules'):
path_info_pop(environ)
+ environ['QUERY_STRING'] = ''
subapp = StaticURLParser(
domain_info.rule_dir)
return subapp(environ, start_response)
-
+
static_path = os.path.join(domain_info.static_dir,
path_info.lstrip('/'))
static_fn = self.find_file(static_path)
@@ -55,12 +72,69 @@
# Explicit override of a file
app = FileApp(static_fn)
return app(environ, start_response)
+ try:
+ remote_uris = domain_info.remote_uris
+ except AttributeError:
+ exc = httpexceptions.HTTPNotFound(
+ "This domain has not yet been configured (no remote "
+ "URI has been configured for %s)"
+ % domain_info.domain)
+ return exc(environ, start_response)
+ remote_uri = None
+ current_uri = construct_url(environ)
+ for redirect_info in domain_info.redirects:
+ if 'path' in redirect_info:
+ if redirect_info['path'] == path_info:
+ new = redirect_info['rewrite']
+ new = urlparse.urljoin(current_uri, redirect_info['rewrite'])
+ exc = httpexceptions.HTTPMovedPermanently(
+ headers=[('Location', new)])
+ return exc(environ, start_response)
+ elif 'prefix' in redirect_info:
+ assert redirect_info['prefix'].endswith('/')
+ new_uri = None
+ if path_info == redirect_info['prefix'][:-1]:
+ new_uri = redirect_info['rewrite'] + '/'
+ elif path_info.startswith(redirect_info['prefix']):
+ new_uri = redirect_info['rewrite']
+ if not new_uri.endswith('/'):
+ new_uri += '/'
+ new_uri += path_info[len(redirect_info['prefix']):]
+ if new_uri is not None:
+ new_uri = urlparse.urljoin(current_uri, new_uri)
+ exc = httpexceptions.HTTPMovedPermanently(
+ headers=[('Location', new_uri)])
+ return exc(environ, start_response)
+
+ for remote_uri_info in remote_uris:
+ path = remote_uri_info['path']
+ if not path.endswith('/'):
+ path += '/'
+ if path_info + '/' == path:
+ exc = httpexceptions.HTTPMovedPermanently(
+ headers=[('location', construct_url(environ, path_info=path_info+'/'))])
+ return exc(environ, start_response)
+ if path_info.startswith(path):
+ # Found a match
+ remote_uri = remote_uri_info['remote_uri']
+ environ['SCRIPT_NAME'] += path[:-1]
+ environ['PATH_INFO'] = path_info[len(path)-1:]
+ break
+ if not remote_uri:
+ exc = httpexceptions.HTTPNotFound(
+ "No URL is mapped to %r (somewhat oddly); only %s prefixes "
+ "are available"
+ % (path_info, ', '.join([repr(r['path'])
+ for r in remote_uris])))
+ return exc(environ, start_response)
+ print 'Proxy to %r with %r (SCRIPT_NAME=%r)' % (
+ remote_uri, construct_url(environ), environ['SCRIPT_NAME'])
app = proxyapp.ForcedProxy(
- remote=domain_info.remote,
+ remote=remote_uri,
force_host=True)
if self.rewrite_links:
app = relocateresponse.RelocateMiddleware(
- app, old_href=domain_info.remote)
+ app, old_href=remote_uri)
rule_uri = construct_url(
environ, with_query_string=False,
path_info='/_rules/rule.xml')
Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py
==============================================================================
--- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py (original)
+++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py Sun Feb 4 04:30:14 2007
@@ -3,6 +3,7 @@
#from paste.deploy.config import ConfigMiddleware
from paste.deploy.converters import asbool
from paste.recursive import RecursiveMiddleware
+from paste.registry import RegistryManager
from wsgifilter import proxyapp
from paste.exceptions.errormiddleware import ErrorMiddleware
@@ -12,6 +13,7 @@
"""Create a WSGI application and return it"""
app = DeliveranceDispatcher(app_conf)
app = RecursiveMiddleware(app)
+ app = RegistryManager(app)
debug = app_conf['debug'] = asbool(app_conf.get('debug', global_conf.get('debug')))
if asbool(app_conf.get('debug_headers')):
app = proxyapp.DebugHeaders(app, show_body=asbool(app_conf.get('debug_bodies')))
Modified: z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py
==============================================================================
--- z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py (original)
+++ z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py Sun Feb 4 04:30:14 2007
@@ -4,15 +4,21 @@
from paste.urlparser import StaticURLParser
from dvhoster.wsgiapp import make_app
import wsgi_intercept
+import httplib
+for attr in ['get_app', 'connect']:
+ setattr(httplib.HTTPConnection, attr,
+ getattr(wsgi_intercept.WSGI_HTTPConnection, attr).im_func)
data_filename = os.path.join(os.path.dirname(__file__), 'test-data')
wsgi_app = make_app({}, data_dir=data_filename)
app = TestApp(wsgi_app)
def put(uri, data):
- app.post(uri, data, extra_environ={'REQUEST_METHOD': 'PUT'}, status=(201, 204))
+ return app.post(
+ uri, data, extra_environ={'REQUEST_METHOD': 'PUT'},
+ status=(201, 204))
-rule_data = '''
+rule_data = '''\
<?xml version="1.0" encoding="UTF-8"?>
<rules xmlns="http://www.plone.org/deliverance">
<append-or-replace theme="//head" content="//head/title" />
@@ -53,7 +59,9 @@
assert res.body == uri
# Rules:
- put('/.deliverance/rules/rules.xml', rule_data)
+ put('/.deliverance/rules/rule.xml', rule_data)
+ assert app.get('/.deliverance/rules/rule.xml').body == rule_data
+ assert app.get('/_rules/rule.xml').body == rule_data
# Domain (no rename test):
res = app.get('/.deliverance/domain')
@@ -65,11 +73,12 @@
assert app.get('/.deliverance/aliases').body == data
data = '''
- [{"path": "/", "remote_uri": "http://wsgify.org/"},
- {"path": "/bar", "remote_uri": "http://wsgify.org/blah", "comment": "x"}]
+ [{"path": "/bar", "remote_uri": "http://wsgify.org/blah", "comment": "x"},
+ {"path": "/", "remote_uri": "http://wsgify.org/"}]
'''
put('/.deliverance/remote_uris', data)
+ # It gets normalized, so it doesn't actually stay the same:
#assert app.get('/.deliverance/remote_uris').body == data
data = '''
@@ -85,7 +94,7 @@
assert res.body == data
app.get('/.deliverance/static/subdir',
extra_environ={'REQUEST_METHOD': 'MKCOL'}, status=201)
- put('/.deliverance/static/subdir/foo.html', 'blah')
+ res = put('/.deliverance/static/subdir/foo.html', 'blah')
res = app.get('/.deliverance/static/subdir/foo.html')
assert res.body == 'blah'
res = app.get('/subdir/foo.html')
@@ -106,14 +115,14 @@
assert 'unthemed' not in res
# Redirect non-canonical domains:
res = app.get('/index.html', extra_environ=dict(HTTP_HOST='example.com'),
- status=303)
+ status=301)
assert res.header('location') == 'http://%s/index.html' % hostname
# Test the path backend redirect:
res = app.get('/bar/foo.html')
res.mustcontain('foo')
- res = app.get('/test1.html', status=303)
- assert res.header('location') == 'http://%s/test1' % hostname
- res = app.get('/test2/other/stuff.html', status=303)
+ res = app.get('/test1.html', status=301)
+ assert res.header('location') == 'http://%s/test1/' % hostname
+ res = app.get('/test2/other/stuff.html', status=301)
assert res.header('location') == 'http://%s/test3/other/stuff.html' % hostname
res = app.get('/test1.html/foo/bar', status=404)
res = app.get('/data.html')
@@ -128,4 +137,4 @@
res = app.get('/.deliverance/aliases')
res.mustcontain('localhost')
assert app.get('/.deliverance/domain').body == 'localhost2'
-
+ print 'site renamed to localhost2'
More information about the z3-checkins
mailing list