Merge lp:~oubiwann/txaws/416109-arbitrary-endpoints into lp:~txawsteam/txaws/trunk

Proposed by Duncan McGreggor
Status: Merged
Merge reported by: Duncan McGreggor
Merged at revision: not available
Proposed branch: lp:~oubiwann/txaws/416109-arbitrary-endpoints
Merge into: lp:~txawsteam/txaws/trunk
Diff against target: None lines
To merge this branch: bzr merge lp:~oubiwann/txaws/416109-arbitrary-endpoints
Reviewer Review Type Date Requested Status
Jamu Kakar (community) Approve
Thomas Herve (community) Approve
Review via email: mp+10671@code.launchpad.net

This proposal supersedes a proposal from 2009-08-23.

To post a comment you must log in.
Revision history for this message
Duncan McGreggor (oubiwann) wrote : Posted in a previous version of this proposal

This branch adds support for a service object that manages host endpoints as well as authorization keys (thus obviating the need for the AWSCredential object).

Revision history for this message
Robert Collins (lifeless) wrote : Posted in a previous version of this proposal

On Thu, 2009-08-20 at 19:25 +0000, Duncan McGreggor wrote:
> Duncan McGreggor has proposed merging
> lp:~oubiwann/txaws/416109-arbitrary-endpoints into lp:txaws.
>
> Requested reviews:
> txAWS Team (txawsteam)
>
> This branch adds support for a service object that manages host
> endpoints as well as authorization keys (thus obviating the need for
> the AWSCredential object).

Lets be careful to keep space under storage, ec2 etc for server
components. storage.service isn't really a storage service :) Lets call
the description of an end point AWSServiceEndpoint, or something like
that.

local and credentials appear orthogonal to me - for instance,
EC2 EU and EC2 US are different endpoints/services with common
credentials. I think conflating them is unnecessary and undesirable.
Further to that, the AWSCredentials are usable on related services in a
single region - EC2, S3 and so on, so when we're passing around a
description, we probably want to have a region that describes the
endpoints for a collection of services. The goal being able to have a
static object
AWS_US1 = #...
AWS_US2 = #...
and for people to make their own;
my_eucalyptus_region = #...

At runtime then, one would ask a region for a client of a particular
service, using some credentials.

AWS_US1.make_ec2_client(my_creds)
AWS_US1.make_sqs_client(my_creds)

etc.

We could do this without changing the existing clients at all, by just
storing scheme,host tuples in a AWSRegion - but I think it is cleaner to
do the sort of refactoring you have done. I think it would be best by
having an AWSServiceEndpoint which has the scheme and url, and keeping
the creds separate. For instance,
class AWSServiceRegion:
    def make_ec2_client(self, creds=None):
        return EC2Client(creds=creds, service_endpoint=self.ec2_endpoint)

Also a bit of detail review - 'default_schema = https' - in URL terms
(see http://www.ietf.org/rfc/rfc3986.txt) that is a _scheme_, not a
_schema_.

review needsfixing

Revision history for this message
Duncan McGreggor (oubiwann) wrote : Posted in a previous version of this proposal

> On Thu, 2009-08-20 at 19:25 +0000, Duncan McGreggor wrote:
> > Duncan McGreggor has proposed merging
> > lp:~oubiwann/txaws/416109-arbitrary-endpoints into lp:txaws.
> >
> > Requested reviews:
> > txAWS Team (txawsteam)
> >
> > This branch adds support for a service object that manages host
> > endpoints as well as authorization keys (thus obviating the need for
> > the AWSCredential object).
>
>
> Lets be careful to keep space under storage, ec2 etc for server
> components. storage.service isn't really a storage service :) Lets call
> the description of an end point AWSServiceEndpoint, or something like
> that.
>
> local and credentials appear orthogonal to me - for instance,
> EC2 EU and EC2 US are different endpoints/services with common
> credentials. I think conflating them is unnecessary and undesirable.
> Further to that, the AWSCredentials are usable on related services in a
> single region - EC2, S3 and so on, so when we're passing around a
> description, we probably want to have a region that describes the
> endpoints for a collection of services. The goal being able to have a
> static object
> AWS_US1 = #...
> AWS_US2 = #...
> and for people to make their own;
> my_eucalyptus_region = #...
>
> At runtime then, one would ask a region for a client of a particular
> service, using some credentials.
>
> AWS_US1.make_ec2_client(my_creds)
> AWS_US1.make_sqs_client(my_creds)
>
> etc.
>
> We could do this without changing the existing clients at all, by just
> storing scheme,host tuples in a AWSRegion - but I think it is cleaner to
> do the sort of refactoring you have done. I think it would be best by
> having an AWSServiceEndpoint which has the scheme and url, and keeping
> the creds separate. For instance,
> class AWSServiceRegion:
> def make_ec2_client(self, creds=None):
> return EC2Client(creds=creds, service_endpoint=self.ec2_endpoint)
>
> Also a bit of detail review - 'default_schema = https' - in URL terms
> (see http://www.ietf.org/rfc/rfc3986.txt) that is a _scheme_, not a
> _schema_.
>
> review needsfixing

+1 on these suggestions. I'll give it another go with this in mind.

Revision history for this message
Duncan McGreggor (oubiwann) wrote : Posted in a previous version of this proposal

> On Thu, 2009-08-20 at 19:25 +0000, Duncan McGreggor wrote:
> > Duncan McGreggor has proposed merging
> > lp:~oubiwann/txaws/416109-arbitrary-endpoints into lp:txaws.
> >
> > Requested reviews:
> > txAWS Team (txawsteam)
> >
> > This branch adds support for a service object that manages host
> > endpoints as well as authorization keys (thus obviating the need for
> > the AWSCredential object).
>
>
> Lets be careful to keep space under storage, ec2 etc for server
> components. storage.service isn't really a storage service :) Lets call
> the description of an end point AWSServiceEndpoint, or something like
> that.

[1] Renamed.

> local and credentials appear orthogonal to me - for instance,
> EC2 EU and EC2 US are different endpoints/services with common
> credentials. I think conflating them is unnecessary and undesirable.
> Further to that, the AWSCredentials are usable on related services in a
> single region - EC2, S3 and so on, so when we're passing around a
> description, we probably want to have a region that describes the
> endpoints for a collection of services.

[2]

Brought the credentials back into the source. Pulled credential code out of service endpoint code.

> The goal being able to have a
> static object
> AWS_US1 = #...
> AWS_US2 = #...
> and for people to make their own;
> my_eucalyptus_region = #...
>
> At runtime then, one would ask a region for a client of a particular
> service, using some credentials.
>
> AWS_US1.make_ec2_client(my_creds)
> AWS_US1.make_sqs_client(my_creds)
>
> etc.
>
> We could do this without changing the existing clients at all, by just
> storing scheme,host tuples in a AWSRegion - but I think it is cleaner to
> do the sort of refactoring you have done. I think it would be best by
> having an AWSServiceEndpoint which has the scheme and url, and keeping
> the creds separate. For instance,
> class AWSServiceRegion:
> def make_ec2_client(self, creds=None):
> return EC2Client(creds=creds, service_endpoint=self.ec2_endpoint)

[3]

I'm got an implementation of this in place right now. It ended up pretty similar to what you suggested. There are some missing unit tests right now -- I'll be hitting those this afternoon.

> Also a bit of detail review - 'default_schema = https' - in URL terms
> (see http://www.ietf.org/rfc/rfc3986.txt) that is a _scheme_, not a
> _schema_.

[4]

Ugh, thanks. The first place I wrote it was good, then I copied a typo everywhere else. Fixed.

Revision history for this message
Duncan McGreggor (oubiwann) wrote : Posted in a previous version of this proposal

Okay! Just pushed up the latest code for the missing unit tests. It's ready for another review :-)

Revision history for this message
Robert Collins (lifeless) wrote : Posted in a previous version of this proposal

On Sun, 2009-08-23 at 21:30 +0000, Robert Collins wrote:
> === modified file 'txaws/client/gui/gtk.py'
> --- txaws/client/gui/gtk.py 2009-08-18 22:53:53 +0000
> +++ txaws/client/gui/gtk.py 2009-08-20 19:14:32 +0000
> @@ -8,7 +8,7 @@
> import gobject
> import gtk
>
> -from txaws.credentials import AWSCredentials
> +from txaws.ec2.service import EC2Service
>
>
> __all__ = ['main']
> @@ -27,10 +27,10 @@
> # Nested import because otherwise we get 'reactor already
> installed'.
> self.password_dialog = None
> try:
> - creds = AWSCredentials()
> + service = AWSService()

This is going to be a NameError :P

...
> === added file 'txaws/credentials.py'

this file already exists. I think you've done something weird in your
branch. lets review once thats fixed. Find me in #bzr :P

review needsfixing

--

32. By Duncan McGreggor

- Added access and secret key parameters to the AWSServiceRegion constructor.
- Updated AWSServiceRegion to create creds based on access and secret key if no
  creds are supplied.
- Updated docstrings.

33. By Duncan McGreggor

Added a uri parameter for service region creation to ease the creation of
service region objects with non-Amazon endpoints (e.g., in Landscape).

34. By Duncan McGreggor

Swapped the ordering of an import to be in alphabetical order.

35. By Duncan McGreggor

- Fixed the creds parameter in the get_ec2_client method.
- Removed redundant code in check_parsed_instances.
- Created a testing subpackage for generally useful testing classes.
- Added fake ec2 client and region classes.
- Moved base test case into new testing module.

Revision history for this message
Thomas Herve (therve) wrote :

[1] Some flakes
txaws/ec2/client.py:6: 'b64encode' imported but unused
txaws/storage/client.py:10: 'b64encode' imported but unused
txaws/storage/client.py:11: 'md5' imported but unused
txaws/storage/client.py:18: 'AWSCredentials' imported but unused
txaws/tests/test_credentials.py:6: 'TestCase' imported but unused

[2]
+ try:
+ gtk2reactor.install()
+ except AssertionError:
+ pass

Really? What's the reason behind this change?

[3]
             key_id = content.get_children()[0].get_children()[1].get_text()
             secret_key = content.get_children()[1].get_children()[1].get_text()
             creds = AWSCredentials(access_key=key_id, secret_key=secret_key)
+ region = AWSServiceRegion(creds=creds)
             self.create_client(creds)
             gnomekeyring.item_create_sync(
                 None,

This change seems unecessary.

[4]
+from twisted.web.client import _parse

It's pretty sad, but you can't import that, it's private. You have to copy the function.

[5]
+ self.assertEquals(client1, client2)
+
+
+ def test_get_s3_client(self):

There is an extra blank line here.

Nice branch, +1!

review: Approve
Revision history for this message
Jamu Kakar (jkakar) wrote :

[1]

+ def get_s3_client(self):
+ raise NotImplementedError
+
+ def get_simpledb_client(self):
+ raise NotImplementedError
+
+ def get_sqs_client(self):
+ raise NotImplementedError

I think you should leave these out since they don't add any useful
functionality.

[2]

You have:

+ self.endpoint = endpoint or self.get_uri()

and then a bit later:

     def get_uri(self):
- return self.root_uri + self.get_uri_path()
+ self.endpoint.set_path(self.get_path())
+ return self.endpoint.get_uri()

When endpoint is None, the default situation, an AttributeError will
be raised because self.endpoint is not defined when get_uri tries to
access it. It looks like get_uri should be instantiating and
returning a new endpoint instance, instead of mutating the existing
one. It would be good to add a test to ensure defaults are handled
properly.

[3]

+ def stash_environ(self):
+ self.orig_environ = dict(os.environ)
+ self.addCleanup(self.restore_environ)
+ if 'AWS_ACCESS_KEY_ID' in os.environ:
+ del os.environ['AWS_ACCESS_KEY_ID']
+ if 'AWS_SECRET_ACCESS_KEY' in os.environ:
+ del os.environ['AWS_SECRET_ACCESS_KEY']
+
+ def restore_environ(self):
+ for key in set(os.environ) - set(self.orig_environ):
+ del os.environ[key]
+ os.environ.update(self.orig_environ)

I think these methods should be private. It's not safe for a user
to call stash_environ during a test because it will rewrite
self.orig_environ with the test environment, defeating the purpose
of this code.

If you have already seen it you may want to check out
https://launchpad.net/testresources. It provides a nice framework
for writing helpers that provide per-test services. It could be
useful for doing the kind of thing you're doing above (though maybe
testscenarios is more relevant here?).

[4]

+ def __init__(self, creds, endpoint, instances=[]):

The default list value here will be shared by all FakeEC2Client
instances, which could cause tricky bugs. I recommend you use None
as the default here along with 'self.instances = instances or []'.

[5]

+ def setUp(self):
+ self.addCleanup(self.clean_environment)
+
+ def clean_environment(self):
+ if os.environ.has_key(ENV_ACCESS_KEY):
+ del os.environ[ENV_ACCESS_KEY]
+ if os.environ.has_key(ENV_SECRET_KEY):
+ del os.environ[ENV_SECRET_KEY]

This code is unnecessary given the {stash,restore}_environ methods
in TXAWSTestCase.

Looks good, +1!

review: Approve
36. By Duncan McGreggor

- Removed unimplemented methods (jkakar 1).
- Made environment mutation methods private (jkakar 3).
- Tweaked the default values for the FakeEC2Client (jkakar 4).
- Removed unnecessary test case methods (jkakar 5).

37. By Duncan McGreggor

Tweaked the storage request object's enpoint/uri stuff and added some unit
tests (jkakar 2).

38. By Duncan McGreggor

Removed unnecessary region instantiation (therve 3).
Added parse utility function (therve 4).

39. By Duncan McGreggor

Fixed pyflakes (therve 1).

Revision history for this message
Duncan McGreggor (oubiwann) wrote :

> [1] Some flakes
> txaws/ec2/client.py:6: 'b64encode' imported but unused
> txaws/storage/client.py:10: 'b64encode' imported but unused
> txaws/storage/client.py:11: 'md5' imported but unused
> txaws/storage/client.py:18: 'AWSCredentials' imported but unused
> txaws/tests/test_credentials.py:6: 'TestCase' imported but unused

Fixed.

> [2]
> + try:
> + gtk2reactor.install()
> + except AssertionError:
> + pass
>
> Really? What's the reason behind this change?

I was getting errors in the unit tests without this. I've just commented it out, and run it again, but I'm not longer getting the errors. Dunno. Removed.

> [3]
> key_id = content.get_children()[0].get_children()[1].get_text()
> secret_key =
> content.get_children()[1].get_children()[1].get_text()
> creds = AWSCredentials(access_key=key_id, secret_key=secret_key)
> + region = AWSServiceRegion(creds=creds)
> self.create_client(creds)
> gnomekeyring.item_create_sync(
> None,
>
> This change seems unecessary.

Yup, it is. Removed.

> [4]
> +from twisted.web.client import _parse
>
> It's pretty sad, but you can't import that, it's private. You have to copy the
> function.

Copied this function and its unit tests from twisted.

> [5]
> + self.assertEquals(client1, client2)
> +
> +
> + def test_get_s3_client(self):
>
> There is an extra blank line here.

This was removed with the changes I made from Jamu's comments.

40. By Duncan McGreggor

Removed unneeded try/except block in gtk client (therve 2).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'txaws/client/gui/gtk.py'
--- txaws/client/gui/gtk.py 2009-08-18 22:53:53 +0000
+++ txaws/client/gui/gtk.py 2009-08-25 16:04:05 +0000
@@ -9,6 +9,7 @@
9import gtk9import gtk
1010
11from txaws.credentials import AWSCredentials11from txaws.credentials import AWSCredentials
12from txaws.service import AWSServiceRegion
1213
1314
14__all__ = ['main']15__all__ = ['main']
@@ -30,6 +31,7 @@
30 creds = AWSCredentials()31 creds = AWSCredentials()
31 except ValueError:32 except ValueError:
32 creds = self.from_gnomekeyring()33 creds = self.from_gnomekeyring()
34 self.region = AWSServiceRegion(creds)
33 self.create_client(creds)35 self.create_client(creds)
34 menu = '''36 menu = '''
35 <ui>37 <ui>
@@ -55,9 +57,8 @@
55 self.connect('popup-menu', self.on_popup_menu)57 self.connect('popup-menu', self.on_popup_menu)
5658
57 def create_client(self, creds):59 def create_client(self, creds):
58 from txaws.ec2.client import EC2Client
59 if creds is not None:60 if creds is not None:
60 self.client = EC2Client(creds=creds)61 self.client = self.region.get_ec2_client()
61 self.on_activate(None)62 self.on_activate(None)
62 else:63 else:
63 # waiting on user entered credentials.64 # waiting on user entered credentials.
@@ -134,6 +135,7 @@
134 key_id = content.get_children()[0].get_children()[1].get_text()135 key_id = content.get_children()[0].get_children()[1].get_text()
135 secret_key = content.get_children()[1].get_children()[1].get_text()136 secret_key = content.get_children()[1].get_children()[1].get_text()
136 creds = AWSCredentials(access_key=key_id, secret_key=secret_key)137 creds = AWSCredentials(access_key=key_id, secret_key=secret_key)
138 region = AWSServiceRegion(creds=creds)
137 self.create_client(creds)139 self.create_client(creds)
138 gnomekeyring.item_create_sync(140 gnomekeyring.item_create_sync(
139 None,141 None,
@@ -196,7 +198,10 @@
196 """198 """
197 if reactor is None:199 if reactor is None:
198 from twisted.internet import gtk2reactor200 from twisted.internet import gtk2reactor
199 gtk2reactor.install()201 try:
202 gtk2reactor.install()
203 except AssertionError:
204 pass
200 from twisted.internet import reactor205 from twisted.internet import reactor
201 status = AWSStatusIcon(reactor)206 status = AWSStatusIcon(reactor)
202 gobject.set_application_name('aws-status')207 gobject.set_application_name('aws-status')
203208
=== modified file 'txaws/credentials.py'
--- txaws/credentials.py 2009-08-17 11:18:56 +0000
+++ txaws/credentials.py 2009-08-25 14:36:20 +0000
@@ -5,32 +5,37 @@
55
6import os6import os
77
8from txaws.util import *8from txaws.util import hmac_sha1
99
1010
11__all__ = ['AWSCredentials']11__all__ = ['AWSCredentials']
1212
1313
14ENV_ACCESS_KEY = "AWS_ACCESS_KEY_ID"
15ENV_SECRET_KEY = "AWS_SECRET_ACCESS_KEY"
16
17
14class AWSCredentials(object):18class AWSCredentials(object):
1519
16 def __init__(self, access_key=None, secret_key=None):20 def __init__(self, access_key="", secret_key=""):
17 """Create an AWSCredentials object.21 """Create an AWSCredentials object.
1822
19 :param access_key: The access key to use. If None the environment23 @param access_key: The access key to use. If None the environment
20 variable AWS_ACCESS_KEY_ID is consulted.24 variable AWS_ACCESS_KEY_ID is consulted.
21 :param secret_key: The secret key to use. If None the environment25 @param secret_key: The secret key to use. If None the environment
22 variable AWS_SECRET_ACCESS_KEY is consulted.26 variable AWS_SECRET_ACCESS_KEY is consulted.
23 """27 """
28 self.access_key = access_key
24 self.secret_key = secret_key29 self.secret_key = secret_key
25 if self.secret_key is None:30 if not self.access_key:
26 self.secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')31 self.access_key = os.environ.get(ENV_ACCESS_KEY)
27 if self.secret_key is None:32 if not self.access_key:
28 raise ValueError('Could not find AWS_SECRET_ACCESS_KEY')33 raise ValueError("Could not find %s" % ENV_ACCESS_KEY)
29 self.access_key = access_key34 # perform checks for secret key
30 if self.access_key is None:35 if not self.secret_key:
31 self.access_key = os.environ.get('AWS_ACCESS_KEY_ID')36 self.secret_key = os.environ.get(ENV_SECRET_KEY)
32 if self.access_key is None:37 if not self.secret_key:
33 raise ValueError('Could not find AWS_ACCESS_KEY_ID')38 raise ValueError("Could not find %s" % ENV_SECRET_KEY)
3439
35 def sign(self, bytes):40 def sign(self, bytes):
36 """Sign some bytes."""41 """Sign some bytes."""
3742
=== modified file 'txaws/ec2/client.py'
--- txaws/ec2/client.py 2009-08-21 03:26:35 +0000
+++ txaws/ec2/client.py 2009-08-25 16:04:05 +0000
@@ -8,7 +8,8 @@
88
9from twisted.web.client import getPage9from twisted.web.client import getPage
1010
11from txaws import credentials11from txaws.credentials import AWSCredentials
12from txaws.service import AWSServiceEndpoint
12from txaws.util import iso8601time, XML13from txaws.util import iso8601time, XML
1314
1415
@@ -77,16 +78,16 @@
7778
78 name_space = '{http://ec2.amazonaws.com/doc/2008-12-01/}'79 name_space = '{http://ec2.amazonaws.com/doc/2008-12-01/}'
7980
80 def __init__(self, creds=None, query_factory=None):81 def __init__(self, creds=None, endpoint=None, query_factory=None):
81 """Create an EC2Client.82 """Create an EC2Client.
8283
83 @param creds: Explicit credentials to use. If None, credentials are84 @param creds: User authentication credentials to use.
84 inferred as per txaws.credentials.AWSCredentials.85 @param endpoint: The service endpoint URI.
86 @param query_factory: The class or function that produces a query
87 object for making requests to the EC2 service.
85 """88 """
86 if creds is None:89 self.creds = creds or AWSCredentials()
87 self.creds = credentials.AWSCredentials()90 self.endpoint = endpoint or AWSServiceEndpoint()
88 else:
89 self.creds = creds
90 if query_factory is None:91 if query_factory is None:
91 self.query_factory = Query92 self.query_factory = Query
92 else:93 else:
@@ -94,7 +95,7 @@
9495
95 def describe_instances(self):96 def describe_instances(self):
96 """Describe current instances."""97 """Describe current instances."""
97 q = self.query_factory('DescribeInstances', self.creds)98 q = self.query_factory('DescribeInstances', self.creds, self.endpoint)
98 d = q.submit()99 d = q.submit()
99 return d.addCallback(self._parse_instances)100 return d.addCallback(self._parse_instances)
100101
@@ -177,7 +178,8 @@
177 instanceset = {}178 instanceset = {}
178 for pos, instance_id in enumerate(instance_ids):179 for pos, instance_id in enumerate(instance_ids):
179 instanceset["InstanceId.%d" % (pos+1)] = instance_id180 instanceset["InstanceId.%d" % (pos+1)] = instance_id
180 q = self.query_factory('TerminateInstances', self.creds, instanceset)181 q = self.query_factory('TerminateInstances', self.creds, self.endpoint,
182 instanceset)
181 d = q.submit()183 d = q.submit()
182 return d.addCallback(self._parse_terminate_instances)184 return d.addCallback(self._parse_terminate_instances)
183185
@@ -200,24 +202,24 @@
200class Query(object):202class Query(object):
201 """A query that may be submitted to EC2."""203 """A query that may be submitted to EC2."""
202204
203 def __init__(self, action, creds, other_params=None, time_tuple=None):205 def __init__(self, action, creds, endpoint, other_params=None,
206 time_tuple=None):
204 """Create a Query to submit to EC2."""207 """Create a Query to submit to EC2."""
208 self.creds = creds
209 self.endpoint = endpoint
205 # Require params (2008-12-01 API):210 # Require params (2008-12-01 API):
206 # Version, SignatureVersion, SignatureMethod, Action, AWSAccessKeyId,211 # Version, SignatureVersion, SignatureMethod, Action, AWSAccessKeyId,
207 # Timestamp || Expires, Signature, 212 # Timestamp || Expires, Signature,
208 self.params = {'Version': '2008-12-01',213 self.params = {
214 'Version': '2008-12-01',
209 'SignatureVersion': '2',215 'SignatureVersion': '2',
210 'SignatureMethod': 'HmacSHA1',216 'SignatureMethod': 'HmacSHA1',
211 'Action': action,217 'Action': action,
212 'AWSAccessKeyId': creds.access_key,218 'AWSAccessKeyId': self.creds.access_key,
213 'Timestamp': iso8601time(time_tuple),219 'Timestamp': iso8601time(time_tuple),
214 }220 }
215 if other_params:221 if other_params:
216 self.params.update(other_params)222 self.params.update(other_params)
217 self.method = 'GET'
218 self.host = 'ec2.amazonaws.com'
219 self.uri = '/'
220 self.creds = creds
221223
222 def canonical_query_params(self):224 def canonical_query_params(self):
223 """Return the canonical query params (used in signing)."""225 """Return the canonical query params (used in signing)."""
@@ -230,18 +232,19 @@
230 """Encode a_string as per the canonicalisation encoding rules.232 """Encode a_string as per the canonicalisation encoding rules.
231233
232 See the AWS dev reference page 90 (2008-12-01 version).234 See the AWS dev reference page 90 (2008-12-01 version).
233 :return: a_string encoded.235 @return: a_string encoded.
234 """236 """
235 return quote(a_string, safe='~')237 return quote(a_string, safe='~')
236238
237 def signing_text(self):239 def signing_text(self):
238 """Return the text to be signed when signing the query."""240 """Return the text to be signed when signing the query."""
239 result = "%s\n%s\n%s\n%s" % (self.method, self.host, self.uri,241 result = "%s\n%s\n%s\n%s" % (self.endpoint.method, self.endpoint.host,
240 self.canonical_query_params())242 self.endpoint.path,
243 self.canonical_query_params())
241 return result244 return result
242245
243 def sign(self):246 def sign(self):
244 """Sign this query using its built in credentials.247 """Sign this query using its built in creds.
245 248
246 This prepares it to be sent, and should be done as the last step before249 This prepares it to be sent, and should be done as the last step before
247 submitting the query. Signing is done automatically - this is a public250 submitting the query. Signing is done automatically - this is a public
@@ -256,9 +259,9 @@
256 def submit(self):259 def submit(self):
257 """Submit this query.260 """Submit this query.
258261
259 :return: A deferred from twisted.web.client.getPage262 @return: A deferred from twisted.web.client.getPage
260 """263 """
261 self.sign()264 self.sign()
262 url = 'http://%s%s?%s' % (self.host, self.uri,265 url = "%s?%s" % (self.endpoint.get_uri(),
263 self.canonical_query_params())266 self.canonical_query_params())
264 return getPage(url, method=self.method)267 return getPage(url, method=self.endpoint.method)
265268
=== modified file 'txaws/ec2/tests/test_client.py'
--- txaws/ec2/tests/test_client.py 2009-08-21 03:26:35 +0000
+++ txaws/ec2/tests/test_client.py 2009-08-25 16:04:05 +0000
@@ -7,6 +7,7 @@
77
8from txaws.credentials import AWSCredentials8from txaws.credentials import AWSCredentials
9from txaws.ec2 import client9from txaws.ec2 import client
10from txaws.service import AWSServiceEndpoint, EC2_ENDPOINT_US
10from txaws.tests import TXAWSTestCase11from txaws.tests import TXAWSTestCase
1112
1213
@@ -117,7 +118,7 @@
117 self.assertEquals(instance.ramdisk_id, "id4")118 self.assertEquals(instance.ramdisk_id, "id4")
118119
119120
120class TestEC2Client(TXAWSTestCase):121class EC2ClientTestCase(TXAWSTestCase):
121 122
122 def test_init_no_creds(self):123 def test_init_no_creds(self):
123 os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'124 os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'
@@ -129,7 +130,7 @@
129 self.assertRaises(ValueError, client.EC2Client)130 self.assertRaises(ValueError, client.EC2Client)
130131
131 def test_init_explicit_creds(self):132 def test_init_explicit_creds(self):
132 creds = 'foo'133 creds = AWSCredentials("foo", "bar")
133 ec2 = client.EC2Client(creds=creds)134 ec2 = client.EC2Client(creds=creds)
134 self.assertEqual(creds, ec2.creds)135 self.assertEqual(creds, ec2.creds)
135136
@@ -162,33 +163,40 @@
162163
163164
164 def test_parse_reservation(self):165 def test_parse_reservation(self):
165 ec2 = client.EC2Client(creds='foo')166 creds = AWSCredentials("foo", "bar")
167 ec2 = client.EC2Client(creds=creds)
166 results = ec2._parse_instances(sample_describe_instances_result)168 results = ec2._parse_instances(sample_describe_instances_result)
167 self.check_parsed_instances(results)169 self.check_parsed_instances(results)
168170
169 def test_describe_instances(self):171 def test_describe_instances(self):
170 class StubQuery(object):172 class StubQuery(object):
171 def __init__(stub, action, creds):173 def __init__(stub, action, creds, endpoint):
172 self.assertEqual(action, 'DescribeInstances')174 self.assertEqual(action, 'DescribeInstances')
173 self.assertEqual('foo', creds)175 self.assertEqual(creds.access_key, "foo")
176 self.assertEqual(creds.secret_key, "bar")
174 def submit(self):177 def submit(self):
175 return succeed(sample_describe_instances_result)178 return succeed(sample_describe_instances_result)
176 ec2 = client.EC2Client(creds='foo', query_factory=StubQuery)179 creds = AWSCredentials("foo", "bar")
180 ec2 = client.EC2Client(creds, query_factory=StubQuery)
177 d = ec2.describe_instances()181 d = ec2.describe_instances()
178 d.addCallback(self.check_parsed_instances)182 d.addCallback(self.check_parsed_instances)
179 return d183 return d
180184
181 def test_terminate_instances(self):185 def test_terminate_instances(self):
182 class StubQuery(object):186 class StubQuery(object):
183 def __init__(stub, action, creds, other_params):187 def __init__(stub, action, creds, endpoint, other_params):
184 self.assertEqual(action, 'TerminateInstances')188 self.assertEqual(action, 'TerminateInstances')
185 self.assertEqual('foo', creds)189 self.assertEqual(creds.access_key, "foo")
190 self.assertEqual(creds.secret_key, "bar")
186 self.assertEqual(191 self.assertEqual(
187 {'InstanceId.1': 'i-1234', 'InstanceId.2': 'i-5678'},192 {'InstanceId.1': 'i-1234', 'InstanceId.2': 'i-5678'},
188 other_params)193 other_params)
189 def submit(self):194 def submit(self):
190 return succeed(sample_terminate_instances_result)195 return succeed(sample_terminate_instances_result)
191 ec2 = client.EC2Client(creds='foo', query_factory=StubQuery)196 creds = AWSCredentials("foo", "bar")
197 endpoint = AWSServiceEndpoint(uri=EC2_ENDPOINT_US)
198 ec2 = client.EC2Client(creds=creds, endpoint=endpoint,
199 query_factory=StubQuery)
192 d = ec2.terminate_instances('i-1234', 'i-5678')200 d = ec2.terminate_instances('i-1234', 'i-5678')
193 def check_transition(changes):201 def check_transition(changes):
194 self.assertEqual([('i-1234', 'running', 'shutting-down'),202 self.assertEqual([('i-1234', 'running', 'shutting-down'),
@@ -196,14 +204,15 @@
196 return d204 return d
197205
198206
199class TestQuery(TXAWSTestCase):207class QueryTestCase(TXAWSTestCase):
200208
201 def setUp(self):209 def setUp(self):
202 TXAWSTestCase.setUp(self)210 TXAWSTestCase.setUp(self)
203 self.creds = AWSCredentials('foo', 'bar')211 self.creds = AWSCredentials('foo', 'bar')
212 self.endpoint = AWSServiceEndpoint(uri=EC2_ENDPOINT_US)
204213
205 def test_init_minimum(self):214 def test_init_minimum(self):
206 query = client.Query('DescribeInstances', self.creds)215 query = client.Query('DescribeInstances', self.creds, self.endpoint)
207 self.assertTrue('Timestamp' in query.params)216 self.assertTrue('Timestamp' in query.params)
208 del query.params['Timestamp']217 del query.params['Timestamp']
209 self.assertEqual(218 self.assertEqual(
@@ -221,7 +230,7 @@
221 self.assertRaises(TypeError, client.Query, None)230 self.assertRaises(TypeError, client.Query, None)
222231
223 def test_init_other_args_are_params(self):232 def test_init_other_args_are_params(self):
224 query = client.Query('DescribeInstances', self.creds,233 query = client.Query('DescribeInstances', self.creds, self.endpoint,
225 {'InstanceId.0': '12345'},234 {'InstanceId.0': '12345'},
226 time_tuple=(2007,11,12,13,14,15,0,0,0))235 time_tuple=(2007,11,12,13,14,15,0,0,0))
227 self.assertEqual(236 self.assertEqual(
@@ -235,7 +244,7 @@
235 query.params)244 query.params)
236245
237 def test_sorted_params(self):246 def test_sorted_params(self):
238 query = client.Query('DescribeInstances', self.creds,247 query = client.Query('DescribeInstances', self.creds, self.endpoint,
239 {'fun': 'games'},248 {'fun': 'games'},
240 time_tuple=(2007,11,12,13,14,15,0,0,0))249 time_tuple=(2007,11,12,13,14,15,0,0,0))
241 self.assertEqual([250 self.assertEqual([
@@ -251,16 +260,16 @@
251 def test_encode_unreserved(self):260 def test_encode_unreserved(self):
252 all_unreserved = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'261 all_unreserved = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
253 'abcdefghijklmnopqrstuvwxyz0123456789-_.~')262 'abcdefghijklmnopqrstuvwxyz0123456789-_.~')
254 query = client.Query('DescribeInstances', self.creds)263 query = client.Query('DescribeInstances', self.creds, self.endpoint)
255 self.assertEqual(all_unreserved, query.encode(all_unreserved))264 self.assertEqual(all_unreserved, query.encode(all_unreserved))
256265
257 def test_encode_space(self):266 def test_encode_space(self):
258 """This may be just 'url encode', but the AWS manual isn't clear."""267 """This may be just 'url encode', but the AWS manual isn't clear."""
259 query = client.Query('DescribeInstances', self.creds)268 query = client.Query('DescribeInstances', self.creds, self.endpoint)
260 self.assertEqual('a%20space', query.encode('a space'))269 self.assertEqual('a%20space', query.encode('a space'))
261270
262 def test_canonical_query(self):271 def test_canonical_query(self):
263 query = client.Query('DescribeInstances', self.creds,272 query = client.Query('DescribeInstances', self.creds, self.endpoint,
264 {'fu n': 'g/ames', 'argwithnovalue':'',273 {'fu n': 'g/ames', 'argwithnovalue':'',
265 'InstanceId.1': 'i-1234'},274 'InstanceId.1': 'i-1234'},
266 time_tuple=(2007,11,12,13,14,15,0,0,0))275 time_tuple=(2007,11,12,13,14,15,0,0,0))
@@ -272,17 +281,17 @@
272 self.assertEqual(expected_query, query.canonical_query_params())281 self.assertEqual(expected_query, query.canonical_query_params())
273282
274 def test_signing_text(self):283 def test_signing_text(self):
275 query = client.Query('DescribeInstances', self.creds,284 query = client.Query('DescribeInstances', self.creds, self.endpoint,
276 time_tuple=(2007,11,12,13,14,15,0,0,0))285 time_tuple=(2007,11,12,13,14,15,0,0,0))
277 signing_text = ('GET\nec2.amazonaws.com\n/\n'286 signing_text = ('GET\n%s\n/\n' % self.endpoint.host +
278 'AWSAccessKeyId=foo&Action=DescribeInstances&'287 'AWSAccessKeyId=foo&Action=DescribeInstances&'
279 'SignatureMethod=HmacSHA1&SignatureVersion=2&'288 'SignatureMethod=HmacSHA1&SignatureVersion=2&'
280 'Timestamp=2007-11-12T13%3A14%3A15Z&Version=2008-12-01')289 'Timestamp=2007-11-12T13%3A14%3A15Z&Version=2008-12-01')
281 self.assertEqual(signing_text, query.signing_text())290 self.assertEqual(signing_text, query.signing_text())
282291
283 def test_sign(self):292 def test_sign(self):
284 query = client.Query('DescribeInstances', self.creds,293 query = client.Query('DescribeInstances', self.creds, self.endpoint,
285 time_tuple=(2007,11,12,13,14,15,0,0,0))294 time_tuple=(2007,11,12,13,14,15,0,0,0))
286 query.sign()295 query.sign()
287 self.assertEqual('4hEtLuZo9i6kuG3TOXvRQNOrE/U=',296 self.assertEqual('JuCpwFA2H4OVF3Ql/lAQs+V6iMc=',
288 query.params['Signature'])297 query.params['Signature'])
289298
=== added file 'txaws/service.py'
--- txaws/service.py 1970-01-01 00:00:00 +0000
+++ txaws/service.py 2009-08-25 16:04:05 +0000
@@ -0,0 +1,100 @@
1# Copyright (C) 2009 Duncan McGreggor <duncan@canonical.com>
2# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
3# Licenced under the txaws licence available at /LICENSE in the txaws source.
4
5import os
6
7from twisted.web.client import _parse
8
9
10
11__all__ = ["AWSServiceEndpoint", "AWSServiceRegion", "REGION_US", "REGION_EU"]
12
13
14REGION_US = "US"
15REGION_EU = "EU"
16EC2_ENDPOINT_US = "https://us-east-1.ec2.amazonaws.com/"
17EC2_ENDPOINT_EU = "https://eu-west-1.ec2.amazonaws.com/"
18DEFAULT_PORT = 80
19
20
21class AWSServiceEndpoint(object):
22 """
23 @param uri: The URL for the service.
24 @param method: The HTTP method used when accessing a service.
25 """
26
27 def __init__(self, uri="", method="GET"):
28 self.host = ""
29 self.port = DEFAULT_PORT
30 self.path = "/"
31 self.method = method
32 self._parse_uri(uri)
33 if not self.scheme:
34 self.scheme = "http"
35
36 def _parse_uri(self, uri):
37 scheme, host, port, path = _parse(
38 str(uri), defaultPort=DEFAULT_PORT)
39 self.scheme = scheme
40 self.host = host
41 self.port = port
42 self.path = path
43
44 def set_path(self, path):
45 self.path = path
46
47 def get_uri(self):
48 """Get a URL representation of the service."""
49 uri = "%s://%s" % (self.scheme, self.host)
50 if self.port and self.port != DEFAULT_PORT:
51 uri = "%s:%s" % (uri, self.port)
52 return uri + self.path
53
54
55class AWSServiceRegion(object):
56 """
57 This object represents a collection of client factories that use the same
58 credentials. With Amazon, this collection is associated with a region
59 (e.g., US or EU).
60 """
61 def __init__(self, creds=None, region=REGION_US):
62 self.creds = creds
63 self._clients = {}
64 if region == REGION_US:
65 ec2_endpoint = EC2_ENDPOINT_US
66 elif region == REGION_EU:
67 ec2_endpoint = EC2_ENDPOINT_EU
68 self.ec2_endpoint = AWSServiceEndpoint(uri=ec2_endpoint)
69
70 def get_client(self, cls, purge_cache=False, *args, **kwds):
71 """
72 This is a general method for getting a client: if present, it is pulled
73 from the cache; if not, a new one is instantiated and then put into the
74 cache. This method should not be called directly, but rather by other
75 client-specific methods (e.g., get_ec2_client).
76 """
77 key = str(cls) + str(args) + str(kwds)
78 instance = self._clients.get(key)
79 if purge_cache or not instance:
80 instance = cls(*args, **kwds)
81 self._clients[key] = instance
82 return instance
83
84 def get_ec2_client(self, creds=None):
85 from txaws.ec2.client import EC2Client
86
87 if creds:
88 self.creds = creds
89 return self.get_client(EC2Client, creds=creds,
90 endpoint=self.ec2_endpoint, query_factory=None)
91
92 def get_s3_client(self):
93 raise NotImplementedError
94
95 def get_simpledb_client(self):
96 raise NotImplementedError
97
98 def get_sqs_client(self):
99 raise NotImplementedError
100
0101
=== modified file 'txaws/storage/client.py'
--- txaws/storage/client.py 2009-08-20 12:15:12 +0000
+++ txaws/storage/client.py 2009-08-21 14:50:25 +0000
@@ -25,19 +25,18 @@
25class S3Request(object):25class S3Request(object):
2626
27 def __init__(self, verb, bucket=None, object_name=None, data='',27 def __init__(self, verb, bucket=None, object_name=None, data='',
28 content_type=None,28 content_type=None, metadata={}, creds=None, endpoint=None):
29 metadata={}, root_uri='https://s3.amazonaws.com', creds=None):
30 self.verb = verb29 self.verb = verb
31 self.bucket = bucket30 self.bucket = bucket
32 self.object_name = object_name31 self.object_name = object_name
33 self.data = data32 self.data = data
34 self.content_type = content_type33 self.content_type = content_type
35 self.metadata = metadata34 self.metadata = metadata
36 self.root_uri = root_uri
37 self.creds = creds35 self.creds = creds
36 self.endpoint = endpoint or self.get_uri()
38 self.date = datetimeToString()37 self.date = datetimeToString()
3938
40 def get_uri_path(self):39 def get_path(self):
41 path = '/'40 path = '/'
42 if self.bucket is not None:41 if self.bucket is not None:
43 path += self.bucket42 path += self.bucket
@@ -46,7 +45,8 @@
46 return path45 return path
4746
48 def get_uri(self):47 def get_uri(self):
49 return self.root_uri + self.get_uri_path()48 self.endpoint.set_path(self.get_path())
49 return self.endpoint.get_uri()
5050
51 def get_headers(self):51 def get_headers(self):
52 headers = {'Content-Length': len(self.data),52 headers = {'Content-Length': len(self.data),
@@ -66,7 +66,7 @@
66 return headers66 return headers
6767
68 def get_canonicalized_resource(self):68 def get_canonicalized_resource(self):
69 return self.get_uri_path()69 return self.get_path()
7070
71 def get_canonicalized_amz_headers(self, headers):71 def get_canonicalized_amz_headers(self, headers):
72 result = ''72 result = ''
@@ -76,12 +76,12 @@
76 return ''.join('%s:%s\n' % (name, value) for name, value in headers)76 return ''.join('%s:%s\n' % (name, value) for name, value in headers)
7777
78 def get_signature(self, headers):78 def get_signature(self, headers):
79 text = self.verb + '\n'79 text = (self.verb + '\n' +
80 text += headers.get('Content-MD5', '') + '\n'80 headers.get('Content-MD5', '') + '\n' +
81 text += headers.get('Content-Type', '') + '\n'81 headers.get('Content-Type', '') + '\n' +
82 text += headers.get('Date', '') + '\n'82 headers.get('Date', '') + '\n' +
83 text += self.get_canonicalized_amz_headers(headers)83 self.get_canonicalized_amz_headers(headers) +
84 text += self.get_canonicalized_resource()84 self.get_canonicalized_resource())
85 return self.creds.sign(text)85 return self.creds.sign(text)
8686
87 def submit(self):87 def submit(self):
@@ -94,20 +94,21 @@
9494
95class S3(object):95class S3(object):
9696
97 root_uri = 'https://s3.amazonaws.com/'
98 request_factory = S3Request97 request_factory = S3Request
9998
100 def __init__(self, creds):99 def __init__(self, creds, endpoint):
101 self.creds = creds100 self.creds = creds
101 self.endpoint = endpoint
102102
103 def make_request(self, *a, **kw):103 def make_request(self, *a, **kw):
104 """104 """
105 Create a request with the arguments passed in.105 Create a request with the arguments passed in.
106106
107 This uses the request_factory attribute, adding the credentials to the107 This uses the request_factory attribute, adding the creds and endpoint
108 arguments passed in.108 to the arguments passed in.
109 """109 """
110 return self.request_factory(creds=self.creds, *a, **kw)110 return self.request_factory(creds=self.creds, endpoint=self.endpoint,
111 *a, **kw)
111112
112 def _parse_bucket_list(self, response):113 def _parse_bucket_list(self, response):
113 """114 """
114115
=== modified file 'txaws/storage/tests/test_client.py'
--- txaws/storage/tests/test_client.py 2009-08-20 12:15:12 +0000
+++ txaws/storage/tests/test_client.py 2009-08-21 14:50:25 +0000
@@ -5,6 +5,7 @@
5from twisted.internet.defer import succeed5from twisted.internet.defer import succeed
66
7from txaws.credentials import AWSCredentials7from txaws.credentials import AWSCredentials
8from txaws.service import AWSServiceEndpoint
8from txaws.storage.client import S3, S3Request9from txaws.storage.client import S3, S3Request
9from txaws.tests import TXAWSTestCase10from txaws.tests import TXAWSTestCase
10from txaws.util import calculate_md511from txaws.util import calculate_md5
@@ -18,10 +19,10 @@
18 return succeed('')19 return succeed('')
1920
2021
21class RequestTests(TXAWSTestCase):22class RequestTestCase(TXAWSTestCase):
2223
23 creds = AWSCredentials(access_key='0PN5J17HBGZHT7JJ3X82',24 creds = AWSCredentials(access_key='fookeyid', secret_key='barsecretkey')
24 secret_key='uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o')25 endpoint = AWSServiceEndpoint("https://s3.amazonaws.com/")
2526
26 def test_objectRequest(self):27 def test_objectRequest(self):
27 """28 """
@@ -31,18 +32,22 @@
31 DIGEST = 'zhdB6gwvocWv/ourYUWMxA=='32 DIGEST = 'zhdB6gwvocWv/ourYUWMxA=='
3233
33 request = S3Request('PUT', 'somebucket', 'object/name/here', DATA,34 request = S3Request('PUT', 'somebucket', 'object/name/here', DATA,
34 content_type='text/plain', metadata={'foo': 'bar'})35 content_type='text/plain', metadata={'foo': 'bar'},
36 creds=self.creds, endpoint=self.endpoint)
37 request.get_signature = lambda headers: "TESTINGSIG="
35 self.assertEqual(request.verb, 'PUT')38 self.assertEqual(request.verb, 'PUT')
36 self.assertEqual(39 self.assertEqual(
37 request.get_uri(),40 request.get_uri(),
38 'https://s3.amazonaws.com/somebucket/object/name/here')41 'https://s3.amazonaws.com/somebucket/object/name/here')
39 headers = request.get_headers()42 headers = request.get_headers()
40 self.assertNotEqual(headers.pop('Date'), '')43 self.assertNotEqual(headers.pop('Date'), '')
41 self.assertEqual(headers,44 self.assertEqual(
42 {'Content-Type': 'text/plain',45 headers, {
43 'Content-Length': len(DATA),46 'Authorization': 'AWS fookeyid:TESTINGSIG=',
44 'Content-MD5': DIGEST,47 'Content-Type': 'text/plain',
45 'x-amz-meta-foo': 'bar'})48 'Content-Length': len(DATA),
49 'Content-MD5': DIGEST,
50 'x-amz-meta-foo': 'bar'})
46 self.assertEqual(request.data, 'objectData')51 self.assertEqual(request.data, 'objectData')
4752
48 def test_bucketRequest(self):53 def test_bucketRequest(self):
@@ -51,22 +56,27 @@
51 """56 """
52 DIGEST = '1B2M2Y8AsgTpgAmY7PhCfg=='57 DIGEST = '1B2M2Y8AsgTpgAmY7PhCfg=='
5358
54 request = S3Request('GET', 'somebucket')59 request = S3Request('GET', 'somebucket', creds=self.creds,
60 endpoint=self.endpoint)
61 request.get_signature = lambda headers: "TESTINGSIG="
55 self.assertEqual(request.verb, 'GET')62 self.assertEqual(request.verb, 'GET')
56 self.assertEqual(63 self.assertEqual(
57 request.get_uri(), 'https://s3.amazonaws.com/somebucket')64 request.get_uri(), 'https://s3.amazonaws.com/somebucket')
58 headers = request.get_headers()65 headers = request.get_headers()
59 self.assertNotEqual(headers.pop('Date'), '')66 self.assertNotEqual(headers.pop('Date'), '')
60 self.assertEqual(headers,67 self.assertEqual(
61 {'Content-Length': 0,68 headers, {
62 'Content-MD5': DIGEST})69 'Authorization': 'AWS fookeyid:TESTINGSIG=',
70 'Content-Length': 0,
71 'Content-MD5': DIGEST})
63 self.assertEqual(request.data, '')72 self.assertEqual(request.data, '')
6473
65 def test_submit(self):74 def test_submit(self):
66 """75 """
67 Submitting the request should invoke getPage correctly.76 Submitting the request should invoke getPage correctly.
68 """77 """
69 request = StubbedS3Request('GET', 'somebucket')78 request = StubbedS3Request('GET', 'somebucket', creds=self.creds,
79 endpoint=self.endpoint)
7080
71 def _postCheck(result):81 def _postCheck(result):
72 self.assertEqual(result, '')82 self.assertEqual(result, '')
@@ -80,13 +90,14 @@
80 return request.submit().addCallback(_postCheck)90 return request.submit().addCallback(_postCheck)
8191
82 def test_authenticationTestCases(self):92 def test_authenticationTestCases(self):
83 req = S3Request('GET', creds=self.creds)93 request = S3Request('GET', creds=self.creds, endpoint=self.endpoint)
84 req.date = 'Wed, 28 Mar 2007 01:29:59 +0000'94 request.get_signature = lambda headers: "TESTINGSIG="
95 request.date = 'Wed, 28 Mar 2007 01:29:59 +0000'
8596
86 headers = req.get_headers()97 headers = request.get_headers()
87 self.assertEqual(98 self.assertEqual(
88 headers['Authorization'], 99 headers['Authorization'],
89 'AWS 0PN5J17HBGZHT7JJ3X82:jF7L3z/FTV47vagZzhKupJ9oNig=')100 'AWS fookeyid:TESTINGSIG=')
90101
91102
92class InertRequest(S3Request):103class InertRequest(S3Request):
@@ -153,16 +164,18 @@
153 TXAWSTestCase.setUp(self)164 TXAWSTestCase.setUp(self)
154 self.creds = AWSCredentials(165 self.creds = AWSCredentials(
155 access_key='accessKey', secret_key='secretKey')166 access_key='accessKey', secret_key='secretKey')
156 self.s3 = TestableS3(creds=self.creds)167 self.endpoint = AWSServiceEndpoint()
168 self.s3 = TestableS3(creds=self.creds, endpoint=self.endpoint)
157169
158 def test_make_request(self):170 def test_make_request(self):
159 """171 """
160 Test that make_request passes in the service credentials.172 Test that make_request passes in the credentials object.
161 """173 """
162 marker = object()174 marker = object()
163175
164 def _cb(*a, **kw):176 def _cb(*a, **kw):
165 self.assertEqual(kw['creds'], self.creds)177 self.assertEqual(kw['creds'], self.creds)
178 self.assertEqual(kw['endpoint'], self.endpoint)
166 return marker179 return marker
167180
168 self.s3.request_factory = _cb181 self.s3.request_factory = _cb
169182
=== modified file 'txaws/tests/test_credentials.py'
--- txaws/tests/test_credentials.py 2009-08-20 12:15:12 +0000
+++ txaws/tests/test_credentials.py 2009-08-25 14:36:20 +0000
@@ -5,37 +5,49 @@
55
6from twisted.trial.unittest import TestCase6from twisted.trial.unittest import TestCase
77
8from txaws.credentials import AWSCredentials8from txaws.credentials import AWSCredentials, ENV_ACCESS_KEY, ENV_SECRET_KEY
9from txaws.tests import TXAWSTestCase
10
9from txaws.tests import TXAWSTestCase11from txaws.tests import TXAWSTestCase
1012
1113
12class TestCredentials(TXAWSTestCase):14class TestCredentials(TXAWSTestCase):
1315
16 def setUp(self):
17 self.addCleanup(self.clean_environment)
18
19 def clean_environment(self):
20 if os.environ.has_key(ENV_ACCESS_KEY):
21 del os.environ[ENV_ACCESS_KEY]
22 if os.environ.has_key(ENV_SECRET_KEY):
23 del os.environ[ENV_SECRET_KEY]
24
14 def test_no_access_errors(self):25 def test_no_access_errors(self):
15 # Without anything in os.environ, AWSCredentials() blows up26 # Without anything in os.environ, AWSService() blows up
16 os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'27 os.environ[ENV_SECRET_KEY] = "bar"
17 self.assertRaises(Exception, AWSCredentials)28 self.assertRaises(ValueError, AWSCredentials)
1829
19 def test_no_secret_errors(self):30 def test_no_secret_errors(self):
20 # Without anything in os.environ, AWSCredentials() blows up31 # Without anything in os.environ, AWSService() blows up
21 os.environ['AWS_ACCESS_KEY_ID'] = 'bar'32 os.environ[ENV_ACCESS_KEY] = "foo"
22 self.assertRaises(Exception, AWSCredentials)33 self.assertRaises(ValueError, AWSCredentials)
2334
24 def test_found_values_used(self):35 def test_found_values_used(self):
25 os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'36 os.environ[ENV_ACCESS_KEY] = "foo"
26 os.environ['AWS_ACCESS_KEY_ID'] = 'bar'37 os.environ[ENV_SECRET_KEY] = "bar"
27 creds = AWSCredentials()38 service = AWSCredentials()
28 self.assertEqual('foo', creds.secret_key)39 self.assertEqual("foo", service.access_key)
29 self.assertEqual('bar', creds.access_key)40 self.assertEqual("bar", service.secret_key)
41 self.clean_environment()
3042
31 def test_explicit_access_key(self):43 def test_explicit_access_key(self):
32 os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'44 os.environ[ENV_SECRET_KEY] = "foo"
33 creds = AWSCredentials(access_key='bar')45 service = AWSCredentials(access_key="bar")
34 self.assertEqual('foo', creds.secret_key)46 self.assertEqual("foo", service.secret_key)
35 self.assertEqual('bar', creds.access_key)47 self.assertEqual("bar", service.access_key)
3648
37 def test_explicit_secret_key(self):49 def test_explicit_secret_key(self):
38 os.environ['AWS_ACCESS_KEY_ID'] = 'bar'50 os.environ[ENV_ACCESS_KEY] = "bar"
39 creds = AWSCredentials(secret_key='foo')51 service = AWSCredentials(secret_key="foo")
40 self.assertEqual('foo', creds.secret_key)52 self.assertEqual("foo", service.secret_key)
41 self.assertEqual('bar', creds.access_key)53 self.assertEqual("bar", service.access_key)
4254
=== added file 'txaws/tests/test_service.py'
--- txaws/tests/test_service.py 1970-01-01 00:00:00 +0000
+++ txaws/tests/test_service.py 2009-08-25 16:16:22 +0000
@@ -0,0 +1,116 @@
1# Copyright (C) 2009 Duncan McGreggor <duncan@canonical.com>
2# Licenced under the txaws licence available at /LICENSE in the txaws source.
3
4import os
5
6from txaws.credentials import AWSCredentials
7from txaws.ec2.client import EC2Client
8from txaws.service import AWSServiceEndpoint, AWSServiceRegion, EC2_ENDPOINT_US
9from txaws.tests import TXAWSTestCase
10
11class AWSServiceEndpointTestCase(TXAWSTestCase):
12
13 def setUp(self):
14 self.endpoint = AWSServiceEndpoint(uri="http://my.service/da_endpoint")
15
16 def test_simple_creation(self):
17 endpoint = AWSServiceEndpoint()
18 self.assertEquals(endpoint.scheme, "http")
19 self.assertEquals(endpoint.host, "")
20 self.assertEquals(endpoint.port, 80)
21 self.assertEquals(endpoint.path, "/")
22 self.assertEquals(endpoint.method, "GET")
23
24 def test_parse_uri(self):
25 self.assertEquals(self.endpoint.scheme, "http")
26 self.assertEquals(self.endpoint.host, "my.service")
27 self.assertEquals(self.endpoint.port, 80)
28 self.assertEquals(self.endpoint.path, "/da_endpoint")
29
30 def test_parse_uri_https_and_custom_port(self):
31 endpoint = AWSServiceEndpoint(uri="https://my.service:8080/endpoint")
32 self.assertEquals(endpoint.scheme, "https")
33 self.assertEquals(endpoint.host, "my.service")
34 self.assertEquals(endpoint.port, 8080)
35 self.assertEquals(endpoint.path, "/endpoint")
36
37 def test_custom_method(self):
38 endpoint = AWSServiceEndpoint(uri="http://service/endpoint",
39 method="PUT")
40 self.assertEquals(endpoint.method, "PUT")
41
42 def test_get_uri(self):
43 uri = self.endpoint.get_uri()
44 self.assertEquals(uri, "http://my.service/da_endpoint")
45
46 def test_get_uri_custom_port(self):
47 uri = "https://my.service:8080/endpoint"
48 endpoint = AWSServiceEndpoint(uri=uri)
49 new_uri = endpoint.get_uri()
50 self.assertEquals(new_uri, uri)
51
52 def test_set_path(self):
53 original_path = self.endpoint.path
54 self.endpoint.set_path("/newpath")
55 self.assertEquals(
56 self.endpoint.get_uri(),
57 "http://my.service/newpath")
58
59
60class AWSServiceRegionTestCase(TXAWSTestCase):
61
62 def setUp(self):
63 self.creds = AWSCredentials("foo", "bar")
64 self.region = AWSServiceRegion(creds=self.creds)
65
66 def test_simple_creation(self):
67 self.assertEquals(self.creds, self.region.creds)
68 self.assertEquals(self.region._clients, {})
69 self.assertEquals(self.region.ec2_endpoint.get_uri(), EC2_ENDPOINT_US)
70
71 def test_get_client_with_empty_cache(self):
72 key = str(EC2Client) + str(self.creds) + str(self.region.ec2_endpoint)
73 original_client = self.region._clients.get(key)
74 new_client = self.region.get_client(
75 EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint)
76 self.assertEquals(original_client, None)
77 self.assertTrue(isinstance(new_client, EC2Client))
78 self.assertNotEquals(original_client, new_client)
79
80 def test_get_client_from_cache(self):
81 client1 = self.region.get_client(
82 EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint)
83 client2 = self.region.get_client(
84 EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint)
85 self.assertTrue(isinstance(client1, EC2Client))
86 self.assertTrue(isinstance(client2, EC2Client))
87 self.assertEquals(client1, client2)
88
89 def test_get_client_from_cache_with_purge(self):
90 client1 = self.region.get_client(
91 EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint,
92 purge_cache=True)
93 client2 = self.region.get_client(
94 EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint,
95 purge_cache=True)
96 self.assertTrue(isinstance(client1, EC2Client))
97 self.assertTrue(isinstance(client2, EC2Client))
98 self.assertNotEquals(client1, client2)
99
100 def test_get_ec2_client_from_cache(self):
101 client1 = self.region.get_ec2_client(self.creds)
102 client2 = self.region.get_ec2_client(self.creds)
103 self.assertEquals(self.creds, self.region.creds)
104 self.assertTrue(isinstance(client1, EC2Client))
105 self.assertTrue(isinstance(client2, EC2Client))
106 self.assertEquals(client1, client2)
107
108
109 def test_get_s3_client(self):
110 self.assertRaises(NotImplementedError, self.region.get_s3_client)
111
112 def test_get_simpledb_client(self):
113 self.assertRaises(NotImplementedError, self.region.get_simpledb_client)
114
115 def test_get_sqs_client(self):
116 self.assertRaises(NotImplementedError, self.region.get_sqs_client)

Subscribers

People subscribed via source and target branches