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
1=== modified file 'txaws/client/gui/gtk.py'
2--- txaws/client/gui/gtk.py 2009-08-18 22:53:53 +0000
3+++ txaws/client/gui/gtk.py 2009-08-25 16:04:05 +0000
4@@ -9,6 +9,7 @@
5 import gtk
6
7 from txaws.credentials import AWSCredentials
8+from txaws.service import AWSServiceRegion
9
10
11 __all__ = ['main']
12@@ -30,6 +31,7 @@
13 creds = AWSCredentials()
14 except ValueError:
15 creds = self.from_gnomekeyring()
16+ self.region = AWSServiceRegion(creds)
17 self.create_client(creds)
18 menu = '''
19 <ui>
20@@ -55,9 +57,8 @@
21 self.connect('popup-menu', self.on_popup_menu)
22
23 def create_client(self, creds):
24- from txaws.ec2.client import EC2Client
25 if creds is not None:
26- self.client = EC2Client(creds=creds)
27+ self.client = self.region.get_ec2_client()
28 self.on_activate(None)
29 else:
30 # waiting on user entered credentials.
31@@ -134,6 +135,7 @@
32 key_id = content.get_children()[0].get_children()[1].get_text()
33 secret_key = content.get_children()[1].get_children()[1].get_text()
34 creds = AWSCredentials(access_key=key_id, secret_key=secret_key)
35+ region = AWSServiceRegion(creds=creds)
36 self.create_client(creds)
37 gnomekeyring.item_create_sync(
38 None,
39@@ -196,7 +198,10 @@
40 """
41 if reactor is None:
42 from twisted.internet import gtk2reactor
43- gtk2reactor.install()
44+ try:
45+ gtk2reactor.install()
46+ except AssertionError:
47+ pass
48 from twisted.internet import reactor
49 status = AWSStatusIcon(reactor)
50 gobject.set_application_name('aws-status')
51
52=== modified file 'txaws/credentials.py'
53--- txaws/credentials.py 2009-08-17 11:18:56 +0000
54+++ txaws/credentials.py 2009-08-25 14:36:20 +0000
55@@ -5,32 +5,37 @@
56
57 import os
58
59-from txaws.util import *
60+from txaws.util import hmac_sha1
61
62
63 __all__ = ['AWSCredentials']
64
65
66+ENV_ACCESS_KEY = "AWS_ACCESS_KEY_ID"
67+ENV_SECRET_KEY = "AWS_SECRET_ACCESS_KEY"
68+
69+
70 class AWSCredentials(object):
71
72- def __init__(self, access_key=None, secret_key=None):
73+ def __init__(self, access_key="", secret_key=""):
74 """Create an AWSCredentials object.
75
76- :param access_key: The access key to use. If None the environment
77+ @param access_key: The access key to use. If None the environment
78 variable AWS_ACCESS_KEY_ID is consulted.
79- :param secret_key: The secret key to use. If None the environment
80+ @param secret_key: The secret key to use. If None the environment
81 variable AWS_SECRET_ACCESS_KEY is consulted.
82 """
83+ self.access_key = access_key
84 self.secret_key = secret_key
85- if self.secret_key is None:
86- self.secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
87- if self.secret_key is None:
88- raise ValueError('Could not find AWS_SECRET_ACCESS_KEY')
89- self.access_key = access_key
90- if self.access_key is None:
91- self.access_key = os.environ.get('AWS_ACCESS_KEY_ID')
92- if self.access_key is None:
93- raise ValueError('Could not find AWS_ACCESS_KEY_ID')
94+ if not self.access_key:
95+ self.access_key = os.environ.get(ENV_ACCESS_KEY)
96+ if not self.access_key:
97+ raise ValueError("Could not find %s" % ENV_ACCESS_KEY)
98+ # perform checks for secret key
99+ if not self.secret_key:
100+ self.secret_key = os.environ.get(ENV_SECRET_KEY)
101+ if not self.secret_key:
102+ raise ValueError("Could not find %s" % ENV_SECRET_KEY)
103
104 def sign(self, bytes):
105 """Sign some bytes."""
106
107=== modified file 'txaws/ec2/client.py'
108--- txaws/ec2/client.py 2009-08-21 03:26:35 +0000
109+++ txaws/ec2/client.py 2009-08-25 16:04:05 +0000
110@@ -8,7 +8,8 @@
111
112 from twisted.web.client import getPage
113
114-from txaws import credentials
115+from txaws.credentials import AWSCredentials
116+from txaws.service import AWSServiceEndpoint
117 from txaws.util import iso8601time, XML
118
119
120@@ -77,16 +78,16 @@
121
122 name_space = '{http://ec2.amazonaws.com/doc/2008-12-01/}'
123
124- def __init__(self, creds=None, query_factory=None):
125+ def __init__(self, creds=None, endpoint=None, query_factory=None):
126 """Create an EC2Client.
127
128- @param creds: Explicit credentials to use. If None, credentials are
129- inferred as per txaws.credentials.AWSCredentials.
130+ @param creds: User authentication credentials to use.
131+ @param endpoint: The service endpoint URI.
132+ @param query_factory: The class or function that produces a query
133+ object for making requests to the EC2 service.
134 """
135- if creds is None:
136- self.creds = credentials.AWSCredentials()
137- else:
138- self.creds = creds
139+ self.creds = creds or AWSCredentials()
140+ self.endpoint = endpoint or AWSServiceEndpoint()
141 if query_factory is None:
142 self.query_factory = Query
143 else:
144@@ -94,7 +95,7 @@
145
146 def describe_instances(self):
147 """Describe current instances."""
148- q = self.query_factory('DescribeInstances', self.creds)
149+ q = self.query_factory('DescribeInstances', self.creds, self.endpoint)
150 d = q.submit()
151 return d.addCallback(self._parse_instances)
152
153@@ -177,7 +178,8 @@
154 instanceset = {}
155 for pos, instance_id in enumerate(instance_ids):
156 instanceset["InstanceId.%d" % (pos+1)] = instance_id
157- q = self.query_factory('TerminateInstances', self.creds, instanceset)
158+ q = self.query_factory('TerminateInstances', self.creds, self.endpoint,
159+ instanceset)
160 d = q.submit()
161 return d.addCallback(self._parse_terminate_instances)
162
163@@ -200,24 +202,24 @@
164 class Query(object):
165 """A query that may be submitted to EC2."""
166
167- def __init__(self, action, creds, other_params=None, time_tuple=None):
168+ def __init__(self, action, creds, endpoint, other_params=None,
169+ time_tuple=None):
170 """Create a Query to submit to EC2."""
171+ self.creds = creds
172+ self.endpoint = endpoint
173 # Require params (2008-12-01 API):
174 # Version, SignatureVersion, SignatureMethod, Action, AWSAccessKeyId,
175 # Timestamp || Expires, Signature,
176- self.params = {'Version': '2008-12-01',
177+ self.params = {
178+ 'Version': '2008-12-01',
179 'SignatureVersion': '2',
180 'SignatureMethod': 'HmacSHA1',
181 'Action': action,
182- 'AWSAccessKeyId': creds.access_key,
183+ 'AWSAccessKeyId': self.creds.access_key,
184 'Timestamp': iso8601time(time_tuple),
185 }
186 if other_params:
187 self.params.update(other_params)
188- self.method = 'GET'
189- self.host = 'ec2.amazonaws.com'
190- self.uri = '/'
191- self.creds = creds
192
193 def canonical_query_params(self):
194 """Return the canonical query params (used in signing)."""
195@@ -230,18 +232,19 @@
196 """Encode a_string as per the canonicalisation encoding rules.
197
198 See the AWS dev reference page 90 (2008-12-01 version).
199- :return: a_string encoded.
200+ @return: a_string encoded.
201 """
202 return quote(a_string, safe='~')
203
204 def signing_text(self):
205 """Return the text to be signed when signing the query."""
206- result = "%s\n%s\n%s\n%s" % (self.method, self.host, self.uri,
207- self.canonical_query_params())
208+ result = "%s\n%s\n%s\n%s" % (self.endpoint.method, self.endpoint.host,
209+ self.endpoint.path,
210+ self.canonical_query_params())
211 return result
212
213 def sign(self):
214- """Sign this query using its built in credentials.
215+ """Sign this query using its built in creds.
216
217 This prepares it to be sent, and should be done as the last step before
218 submitting the query. Signing is done automatically - this is a public
219@@ -256,9 +259,9 @@
220 def submit(self):
221 """Submit this query.
222
223- :return: A deferred from twisted.web.client.getPage
224+ @return: A deferred from twisted.web.client.getPage
225 """
226 self.sign()
227- url = 'http://%s%s?%s' % (self.host, self.uri,
228- self.canonical_query_params())
229- return getPage(url, method=self.method)
230+ url = "%s?%s" % (self.endpoint.get_uri(),
231+ self.canonical_query_params())
232+ return getPage(url, method=self.endpoint.method)
233
234=== modified file 'txaws/ec2/tests/test_client.py'
235--- txaws/ec2/tests/test_client.py 2009-08-21 03:26:35 +0000
236+++ txaws/ec2/tests/test_client.py 2009-08-25 16:04:05 +0000
237@@ -7,6 +7,7 @@
238
239 from txaws.credentials import AWSCredentials
240 from txaws.ec2 import client
241+from txaws.service import AWSServiceEndpoint, EC2_ENDPOINT_US
242 from txaws.tests import TXAWSTestCase
243
244
245@@ -117,7 +118,7 @@
246 self.assertEquals(instance.ramdisk_id, "id4")
247
248
249-class TestEC2Client(TXAWSTestCase):
250+class EC2ClientTestCase(TXAWSTestCase):
251
252 def test_init_no_creds(self):
253 os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'
254@@ -129,7 +130,7 @@
255 self.assertRaises(ValueError, client.EC2Client)
256
257 def test_init_explicit_creds(self):
258- creds = 'foo'
259+ creds = AWSCredentials("foo", "bar")
260 ec2 = client.EC2Client(creds=creds)
261 self.assertEqual(creds, ec2.creds)
262
263@@ -162,33 +163,40 @@
264
265
266 def test_parse_reservation(self):
267- ec2 = client.EC2Client(creds='foo')
268+ creds = AWSCredentials("foo", "bar")
269+ ec2 = client.EC2Client(creds=creds)
270 results = ec2._parse_instances(sample_describe_instances_result)
271 self.check_parsed_instances(results)
272
273 def test_describe_instances(self):
274 class StubQuery(object):
275- def __init__(stub, action, creds):
276+ def __init__(stub, action, creds, endpoint):
277 self.assertEqual(action, 'DescribeInstances')
278- self.assertEqual('foo', creds)
279+ self.assertEqual(creds.access_key, "foo")
280+ self.assertEqual(creds.secret_key, "bar")
281 def submit(self):
282 return succeed(sample_describe_instances_result)
283- ec2 = client.EC2Client(creds='foo', query_factory=StubQuery)
284+ creds = AWSCredentials("foo", "bar")
285+ ec2 = client.EC2Client(creds, query_factory=StubQuery)
286 d = ec2.describe_instances()
287 d.addCallback(self.check_parsed_instances)
288 return d
289
290 def test_terminate_instances(self):
291 class StubQuery(object):
292- def __init__(stub, action, creds, other_params):
293+ def __init__(stub, action, creds, endpoint, other_params):
294 self.assertEqual(action, 'TerminateInstances')
295- self.assertEqual('foo', creds)
296+ self.assertEqual(creds.access_key, "foo")
297+ self.assertEqual(creds.secret_key, "bar")
298 self.assertEqual(
299 {'InstanceId.1': 'i-1234', 'InstanceId.2': 'i-5678'},
300 other_params)
301 def submit(self):
302 return succeed(sample_terminate_instances_result)
303- ec2 = client.EC2Client(creds='foo', query_factory=StubQuery)
304+ creds = AWSCredentials("foo", "bar")
305+ endpoint = AWSServiceEndpoint(uri=EC2_ENDPOINT_US)
306+ ec2 = client.EC2Client(creds=creds, endpoint=endpoint,
307+ query_factory=StubQuery)
308 d = ec2.terminate_instances('i-1234', 'i-5678')
309 def check_transition(changes):
310 self.assertEqual([('i-1234', 'running', 'shutting-down'),
311@@ -196,14 +204,15 @@
312 return d
313
314
315-class TestQuery(TXAWSTestCase):
316+class QueryTestCase(TXAWSTestCase):
317
318 def setUp(self):
319 TXAWSTestCase.setUp(self)
320 self.creds = AWSCredentials('foo', 'bar')
321+ self.endpoint = AWSServiceEndpoint(uri=EC2_ENDPOINT_US)
322
323 def test_init_minimum(self):
324- query = client.Query('DescribeInstances', self.creds)
325+ query = client.Query('DescribeInstances', self.creds, self.endpoint)
326 self.assertTrue('Timestamp' in query.params)
327 del query.params['Timestamp']
328 self.assertEqual(
329@@ -221,7 +230,7 @@
330 self.assertRaises(TypeError, client.Query, None)
331
332 def test_init_other_args_are_params(self):
333- query = client.Query('DescribeInstances', self.creds,
334+ query = client.Query('DescribeInstances', self.creds, self.endpoint,
335 {'InstanceId.0': '12345'},
336 time_tuple=(2007,11,12,13,14,15,0,0,0))
337 self.assertEqual(
338@@ -235,7 +244,7 @@
339 query.params)
340
341 def test_sorted_params(self):
342- query = client.Query('DescribeInstances', self.creds,
343+ query = client.Query('DescribeInstances', self.creds, self.endpoint,
344 {'fun': 'games'},
345 time_tuple=(2007,11,12,13,14,15,0,0,0))
346 self.assertEqual([
347@@ -251,16 +260,16 @@
348 def test_encode_unreserved(self):
349 all_unreserved = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
350 'abcdefghijklmnopqrstuvwxyz0123456789-_.~')
351- query = client.Query('DescribeInstances', self.creds)
352+ query = client.Query('DescribeInstances', self.creds, self.endpoint)
353 self.assertEqual(all_unreserved, query.encode(all_unreserved))
354
355 def test_encode_space(self):
356 """This may be just 'url encode', but the AWS manual isn't clear."""
357- query = client.Query('DescribeInstances', self.creds)
358+ query = client.Query('DescribeInstances', self.creds, self.endpoint)
359 self.assertEqual('a%20space', query.encode('a space'))
360
361 def test_canonical_query(self):
362- query = client.Query('DescribeInstances', self.creds,
363+ query = client.Query('DescribeInstances', self.creds, self.endpoint,
364 {'fu n': 'g/ames', 'argwithnovalue':'',
365 'InstanceId.1': 'i-1234'},
366 time_tuple=(2007,11,12,13,14,15,0,0,0))
367@@ -272,17 +281,17 @@
368 self.assertEqual(expected_query, query.canonical_query_params())
369
370 def test_signing_text(self):
371- query = client.Query('DescribeInstances', self.creds,
372+ query = client.Query('DescribeInstances', self.creds, self.endpoint,
373 time_tuple=(2007,11,12,13,14,15,0,0,0))
374- signing_text = ('GET\nec2.amazonaws.com\n/\n'
375+ signing_text = ('GET\n%s\n/\n' % self.endpoint.host +
376 'AWSAccessKeyId=foo&Action=DescribeInstances&'
377 'SignatureMethod=HmacSHA1&SignatureVersion=2&'
378 'Timestamp=2007-11-12T13%3A14%3A15Z&Version=2008-12-01')
379 self.assertEqual(signing_text, query.signing_text())
380
381 def test_sign(self):
382- query = client.Query('DescribeInstances', self.creds,
383+ query = client.Query('DescribeInstances', self.creds, self.endpoint,
384 time_tuple=(2007,11,12,13,14,15,0,0,0))
385 query.sign()
386- self.assertEqual('4hEtLuZo9i6kuG3TOXvRQNOrE/U=',
387+ self.assertEqual('JuCpwFA2H4OVF3Ql/lAQs+V6iMc=',
388 query.params['Signature'])
389
390=== added file 'txaws/service.py'
391--- txaws/service.py 1970-01-01 00:00:00 +0000
392+++ txaws/service.py 2009-08-25 16:04:05 +0000
393@@ -0,0 +1,100 @@
394+# Copyright (C) 2009 Duncan McGreggor <duncan@canonical.com>
395+# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
396+# Licenced under the txaws licence available at /LICENSE in the txaws source.
397+
398+import os
399+
400+from twisted.web.client import _parse
401+
402+
403+
404+__all__ = ["AWSServiceEndpoint", "AWSServiceRegion", "REGION_US", "REGION_EU"]
405+
406+
407+REGION_US = "US"
408+REGION_EU = "EU"
409+EC2_ENDPOINT_US = "https://us-east-1.ec2.amazonaws.com/"
410+EC2_ENDPOINT_EU = "https://eu-west-1.ec2.amazonaws.com/"
411+DEFAULT_PORT = 80
412+
413+
414+class AWSServiceEndpoint(object):
415+ """
416+ @param uri: The URL for the service.
417+ @param method: The HTTP method used when accessing a service.
418+ """
419+
420+ def __init__(self, uri="", method="GET"):
421+ self.host = ""
422+ self.port = DEFAULT_PORT
423+ self.path = "/"
424+ self.method = method
425+ self._parse_uri(uri)
426+ if not self.scheme:
427+ self.scheme = "http"
428+
429+ def _parse_uri(self, uri):
430+ scheme, host, port, path = _parse(
431+ str(uri), defaultPort=DEFAULT_PORT)
432+ self.scheme = scheme
433+ self.host = host
434+ self.port = port
435+ self.path = path
436+
437+ def set_path(self, path):
438+ self.path = path
439+
440+ def get_uri(self):
441+ """Get a URL representation of the service."""
442+ uri = "%s://%s" % (self.scheme, self.host)
443+ if self.port and self.port != DEFAULT_PORT:
444+ uri = "%s:%s" % (uri, self.port)
445+ return uri + self.path
446+
447+
448+class AWSServiceRegion(object):
449+ """
450+ This object represents a collection of client factories that use the same
451+ credentials. With Amazon, this collection is associated with a region
452+ (e.g., US or EU).
453+ """
454+ def __init__(self, creds=None, region=REGION_US):
455+ self.creds = creds
456+ self._clients = {}
457+ if region == REGION_US:
458+ ec2_endpoint = EC2_ENDPOINT_US
459+ elif region == REGION_EU:
460+ ec2_endpoint = EC2_ENDPOINT_EU
461+ self.ec2_endpoint = AWSServiceEndpoint(uri=ec2_endpoint)
462+
463+ def get_client(self, cls, purge_cache=False, *args, **kwds):
464+ """
465+ This is a general method for getting a client: if present, it is pulled
466+ from the cache; if not, a new one is instantiated and then put into the
467+ cache. This method should not be called directly, but rather by other
468+ client-specific methods (e.g., get_ec2_client).
469+ """
470+ key = str(cls) + str(args) + str(kwds)
471+ instance = self._clients.get(key)
472+ if purge_cache or not instance:
473+ instance = cls(*args, **kwds)
474+ self._clients[key] = instance
475+ return instance
476+
477+ def get_ec2_client(self, creds=None):
478+ from txaws.ec2.client import EC2Client
479+
480+ if creds:
481+ self.creds = creds
482+ return self.get_client(EC2Client, creds=creds,
483+ endpoint=self.ec2_endpoint, query_factory=None)
484+
485+ def get_s3_client(self):
486+ raise NotImplementedError
487+
488+ def get_simpledb_client(self):
489+ raise NotImplementedError
490+
491+ def get_sqs_client(self):
492+ raise NotImplementedError
493+
494
495=== modified file 'txaws/storage/client.py'
496--- txaws/storage/client.py 2009-08-20 12:15:12 +0000
497+++ txaws/storage/client.py 2009-08-21 14:50:25 +0000
498@@ -25,19 +25,18 @@
499 class S3Request(object):
500
501 def __init__(self, verb, bucket=None, object_name=None, data='',
502- content_type=None,
503- metadata={}, root_uri='https://s3.amazonaws.com', creds=None):
504+ content_type=None, metadata={}, creds=None, endpoint=None):
505 self.verb = verb
506 self.bucket = bucket
507 self.object_name = object_name
508 self.data = data
509 self.content_type = content_type
510 self.metadata = metadata
511- self.root_uri = root_uri
512 self.creds = creds
513+ self.endpoint = endpoint or self.get_uri()
514 self.date = datetimeToString()
515
516- def get_uri_path(self):
517+ def get_path(self):
518 path = '/'
519 if self.bucket is not None:
520 path += self.bucket
521@@ -46,7 +45,8 @@
522 return path
523
524 def get_uri(self):
525- return self.root_uri + self.get_uri_path()
526+ self.endpoint.set_path(self.get_path())
527+ return self.endpoint.get_uri()
528
529 def get_headers(self):
530 headers = {'Content-Length': len(self.data),
531@@ -66,7 +66,7 @@
532 return headers
533
534 def get_canonicalized_resource(self):
535- return self.get_uri_path()
536+ return self.get_path()
537
538 def get_canonicalized_amz_headers(self, headers):
539 result = ''
540@@ -76,12 +76,12 @@
541 return ''.join('%s:%s\n' % (name, value) for name, value in headers)
542
543 def get_signature(self, headers):
544- text = self.verb + '\n'
545- text += headers.get('Content-MD5', '') + '\n'
546- text += headers.get('Content-Type', '') + '\n'
547- text += headers.get('Date', '') + '\n'
548- text += self.get_canonicalized_amz_headers(headers)
549- text += self.get_canonicalized_resource()
550+ text = (self.verb + '\n' +
551+ headers.get('Content-MD5', '') + '\n' +
552+ headers.get('Content-Type', '') + '\n' +
553+ headers.get('Date', '') + '\n' +
554+ self.get_canonicalized_amz_headers(headers) +
555+ self.get_canonicalized_resource())
556 return self.creds.sign(text)
557
558 def submit(self):
559@@ -94,20 +94,21 @@
560
561 class S3(object):
562
563- root_uri = 'https://s3.amazonaws.com/'
564 request_factory = S3Request
565
566- def __init__(self, creds):
567+ def __init__(self, creds, endpoint):
568 self.creds = creds
569+ self.endpoint = endpoint
570
571 def make_request(self, *a, **kw):
572 """
573 Create a request with the arguments passed in.
574
575- This uses the request_factory attribute, adding the credentials to the
576- arguments passed in.
577+ This uses the request_factory attribute, adding the creds and endpoint
578+ to the arguments passed in.
579 """
580- return self.request_factory(creds=self.creds, *a, **kw)
581+ return self.request_factory(creds=self.creds, endpoint=self.endpoint,
582+ *a, **kw)
583
584 def _parse_bucket_list(self, response):
585 """
586
587=== modified file 'txaws/storage/tests/test_client.py'
588--- txaws/storage/tests/test_client.py 2009-08-20 12:15:12 +0000
589+++ txaws/storage/tests/test_client.py 2009-08-21 14:50:25 +0000
590@@ -5,6 +5,7 @@
591 from twisted.internet.defer import succeed
592
593 from txaws.credentials import AWSCredentials
594+from txaws.service import AWSServiceEndpoint
595 from txaws.storage.client import S3, S3Request
596 from txaws.tests import TXAWSTestCase
597 from txaws.util import calculate_md5
598@@ -18,10 +19,10 @@
599 return succeed('')
600
601
602-class RequestTests(TXAWSTestCase):
603+class RequestTestCase(TXAWSTestCase):
604
605- creds = AWSCredentials(access_key='0PN5J17HBGZHT7JJ3X82',
606- secret_key='uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o')
607+ creds = AWSCredentials(access_key='fookeyid', secret_key='barsecretkey')
608+ endpoint = AWSServiceEndpoint("https://s3.amazonaws.com/")
609
610 def test_objectRequest(self):
611 """
612@@ -31,18 +32,22 @@
613 DIGEST = 'zhdB6gwvocWv/ourYUWMxA=='
614
615 request = S3Request('PUT', 'somebucket', 'object/name/here', DATA,
616- content_type='text/plain', metadata={'foo': 'bar'})
617+ content_type='text/plain', metadata={'foo': 'bar'},
618+ creds=self.creds, endpoint=self.endpoint)
619+ request.get_signature = lambda headers: "TESTINGSIG="
620 self.assertEqual(request.verb, 'PUT')
621 self.assertEqual(
622 request.get_uri(),
623 'https://s3.amazonaws.com/somebucket/object/name/here')
624 headers = request.get_headers()
625 self.assertNotEqual(headers.pop('Date'), '')
626- self.assertEqual(headers,
627- {'Content-Type': 'text/plain',
628- 'Content-Length': len(DATA),
629- 'Content-MD5': DIGEST,
630- 'x-amz-meta-foo': 'bar'})
631+ self.assertEqual(
632+ headers, {
633+ 'Authorization': 'AWS fookeyid:TESTINGSIG=',
634+ 'Content-Type': 'text/plain',
635+ 'Content-Length': len(DATA),
636+ 'Content-MD5': DIGEST,
637+ 'x-amz-meta-foo': 'bar'})
638 self.assertEqual(request.data, 'objectData')
639
640 def test_bucketRequest(self):
641@@ -51,22 +56,27 @@
642 """
643 DIGEST = '1B2M2Y8AsgTpgAmY7PhCfg=='
644
645- request = S3Request('GET', 'somebucket')
646+ request = S3Request('GET', 'somebucket', creds=self.creds,
647+ endpoint=self.endpoint)
648+ request.get_signature = lambda headers: "TESTINGSIG="
649 self.assertEqual(request.verb, 'GET')
650 self.assertEqual(
651 request.get_uri(), 'https://s3.amazonaws.com/somebucket')
652 headers = request.get_headers()
653 self.assertNotEqual(headers.pop('Date'), '')
654- self.assertEqual(headers,
655- {'Content-Length': 0,
656- 'Content-MD5': DIGEST})
657+ self.assertEqual(
658+ headers, {
659+ 'Authorization': 'AWS fookeyid:TESTINGSIG=',
660+ 'Content-Length': 0,
661+ 'Content-MD5': DIGEST})
662 self.assertEqual(request.data, '')
663
664 def test_submit(self):
665 """
666 Submitting the request should invoke getPage correctly.
667 """
668- request = StubbedS3Request('GET', 'somebucket')
669+ request = StubbedS3Request('GET', 'somebucket', creds=self.creds,
670+ endpoint=self.endpoint)
671
672 def _postCheck(result):
673 self.assertEqual(result, '')
674@@ -80,13 +90,14 @@
675 return request.submit().addCallback(_postCheck)
676
677 def test_authenticationTestCases(self):
678- req = S3Request('GET', creds=self.creds)
679- req.date = 'Wed, 28 Mar 2007 01:29:59 +0000'
680+ request = S3Request('GET', creds=self.creds, endpoint=self.endpoint)
681+ request.get_signature = lambda headers: "TESTINGSIG="
682+ request.date = 'Wed, 28 Mar 2007 01:29:59 +0000'
683
684- headers = req.get_headers()
685+ headers = request.get_headers()
686 self.assertEqual(
687 headers['Authorization'],
688- 'AWS 0PN5J17HBGZHT7JJ3X82:jF7L3z/FTV47vagZzhKupJ9oNig=')
689+ 'AWS fookeyid:TESTINGSIG=')
690
691
692 class InertRequest(S3Request):
693@@ -153,16 +164,18 @@
694 TXAWSTestCase.setUp(self)
695 self.creds = AWSCredentials(
696 access_key='accessKey', secret_key='secretKey')
697- self.s3 = TestableS3(creds=self.creds)
698+ self.endpoint = AWSServiceEndpoint()
699+ self.s3 = TestableS3(creds=self.creds, endpoint=self.endpoint)
700
701 def test_make_request(self):
702 """
703- Test that make_request passes in the service credentials.
704+ Test that make_request passes in the credentials object.
705 """
706 marker = object()
707
708 def _cb(*a, **kw):
709 self.assertEqual(kw['creds'], self.creds)
710+ self.assertEqual(kw['endpoint'], self.endpoint)
711 return marker
712
713 self.s3.request_factory = _cb
714
715=== modified file 'txaws/tests/test_credentials.py'
716--- txaws/tests/test_credentials.py 2009-08-20 12:15:12 +0000
717+++ txaws/tests/test_credentials.py 2009-08-25 14:36:20 +0000
718@@ -5,37 +5,49 @@
719
720 from twisted.trial.unittest import TestCase
721
722-from txaws.credentials import AWSCredentials
723+from txaws.credentials import AWSCredentials, ENV_ACCESS_KEY, ENV_SECRET_KEY
724+from txaws.tests import TXAWSTestCase
725+
726 from txaws.tests import TXAWSTestCase
727
728
729 class TestCredentials(TXAWSTestCase):
730
731+ def setUp(self):
732+ self.addCleanup(self.clean_environment)
733+
734+ def clean_environment(self):
735+ if os.environ.has_key(ENV_ACCESS_KEY):
736+ del os.environ[ENV_ACCESS_KEY]
737+ if os.environ.has_key(ENV_SECRET_KEY):
738+ del os.environ[ENV_SECRET_KEY]
739+
740 def test_no_access_errors(self):
741- # Without anything in os.environ, AWSCredentials() blows up
742- os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'
743- self.assertRaises(Exception, AWSCredentials)
744+ # Without anything in os.environ, AWSService() blows up
745+ os.environ[ENV_SECRET_KEY] = "bar"
746+ self.assertRaises(ValueError, AWSCredentials)
747
748 def test_no_secret_errors(self):
749- # Without anything in os.environ, AWSCredentials() blows up
750- os.environ['AWS_ACCESS_KEY_ID'] = 'bar'
751- self.assertRaises(Exception, AWSCredentials)
752+ # Without anything in os.environ, AWSService() blows up
753+ os.environ[ENV_ACCESS_KEY] = "foo"
754+ self.assertRaises(ValueError, AWSCredentials)
755
756 def test_found_values_used(self):
757- os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'
758- os.environ['AWS_ACCESS_KEY_ID'] = 'bar'
759- creds = AWSCredentials()
760- self.assertEqual('foo', creds.secret_key)
761- self.assertEqual('bar', creds.access_key)
762+ os.environ[ENV_ACCESS_KEY] = "foo"
763+ os.environ[ENV_SECRET_KEY] = "bar"
764+ service = AWSCredentials()
765+ self.assertEqual("foo", service.access_key)
766+ self.assertEqual("bar", service.secret_key)
767+ self.clean_environment()
768
769 def test_explicit_access_key(self):
770- os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo'
771- creds = AWSCredentials(access_key='bar')
772- self.assertEqual('foo', creds.secret_key)
773- self.assertEqual('bar', creds.access_key)
774+ os.environ[ENV_SECRET_KEY] = "foo"
775+ service = AWSCredentials(access_key="bar")
776+ self.assertEqual("foo", service.secret_key)
777+ self.assertEqual("bar", service.access_key)
778
779 def test_explicit_secret_key(self):
780- os.environ['AWS_ACCESS_KEY_ID'] = 'bar'
781- creds = AWSCredentials(secret_key='foo')
782- self.assertEqual('foo', creds.secret_key)
783- self.assertEqual('bar', creds.access_key)
784+ os.environ[ENV_ACCESS_KEY] = "bar"
785+ service = AWSCredentials(secret_key="foo")
786+ self.assertEqual("foo", service.secret_key)
787+ self.assertEqual("bar", service.access_key)
788
789=== added file 'txaws/tests/test_service.py'
790--- txaws/tests/test_service.py 1970-01-01 00:00:00 +0000
791+++ txaws/tests/test_service.py 2009-08-25 16:16:22 +0000
792@@ -0,0 +1,116 @@
793+# Copyright (C) 2009 Duncan McGreggor <duncan@canonical.com>
794+# Licenced under the txaws licence available at /LICENSE in the txaws source.
795+
796+import os
797+
798+from txaws.credentials import AWSCredentials
799+from txaws.ec2.client import EC2Client
800+from txaws.service import AWSServiceEndpoint, AWSServiceRegion, EC2_ENDPOINT_US
801+from txaws.tests import TXAWSTestCase
802+
803+class AWSServiceEndpointTestCase(TXAWSTestCase):
804+
805+ def setUp(self):
806+ self.endpoint = AWSServiceEndpoint(uri="http://my.service/da_endpoint")
807+
808+ def test_simple_creation(self):
809+ endpoint = AWSServiceEndpoint()
810+ self.assertEquals(endpoint.scheme, "http")
811+ self.assertEquals(endpoint.host, "")
812+ self.assertEquals(endpoint.port, 80)
813+ self.assertEquals(endpoint.path, "/")
814+ self.assertEquals(endpoint.method, "GET")
815+
816+ def test_parse_uri(self):
817+ self.assertEquals(self.endpoint.scheme, "http")
818+ self.assertEquals(self.endpoint.host, "my.service")
819+ self.assertEquals(self.endpoint.port, 80)
820+ self.assertEquals(self.endpoint.path, "/da_endpoint")
821+
822+ def test_parse_uri_https_and_custom_port(self):
823+ endpoint = AWSServiceEndpoint(uri="https://my.service:8080/endpoint")
824+ self.assertEquals(endpoint.scheme, "https")
825+ self.assertEquals(endpoint.host, "my.service")
826+ self.assertEquals(endpoint.port, 8080)
827+ self.assertEquals(endpoint.path, "/endpoint")
828+
829+ def test_custom_method(self):
830+ endpoint = AWSServiceEndpoint(uri="http://service/endpoint",
831+ method="PUT")
832+ self.assertEquals(endpoint.method, "PUT")
833+
834+ def test_get_uri(self):
835+ uri = self.endpoint.get_uri()
836+ self.assertEquals(uri, "http://my.service/da_endpoint")
837+
838+ def test_get_uri_custom_port(self):
839+ uri = "https://my.service:8080/endpoint"
840+ endpoint = AWSServiceEndpoint(uri=uri)
841+ new_uri = endpoint.get_uri()
842+ self.assertEquals(new_uri, uri)
843+
844+ def test_set_path(self):
845+ original_path = self.endpoint.path
846+ self.endpoint.set_path("/newpath")
847+ self.assertEquals(
848+ self.endpoint.get_uri(),
849+ "http://my.service/newpath")
850+
851+
852+class AWSServiceRegionTestCase(TXAWSTestCase):
853+
854+ def setUp(self):
855+ self.creds = AWSCredentials("foo", "bar")
856+ self.region = AWSServiceRegion(creds=self.creds)
857+
858+ def test_simple_creation(self):
859+ self.assertEquals(self.creds, self.region.creds)
860+ self.assertEquals(self.region._clients, {})
861+ self.assertEquals(self.region.ec2_endpoint.get_uri(), EC2_ENDPOINT_US)
862+
863+ def test_get_client_with_empty_cache(self):
864+ key = str(EC2Client) + str(self.creds) + str(self.region.ec2_endpoint)
865+ original_client = self.region._clients.get(key)
866+ new_client = self.region.get_client(
867+ EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint)
868+ self.assertEquals(original_client, None)
869+ self.assertTrue(isinstance(new_client, EC2Client))
870+ self.assertNotEquals(original_client, new_client)
871+
872+ def test_get_client_from_cache(self):
873+ client1 = self.region.get_client(
874+ EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint)
875+ client2 = self.region.get_client(
876+ EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint)
877+ self.assertTrue(isinstance(client1, EC2Client))
878+ self.assertTrue(isinstance(client2, EC2Client))
879+ self.assertEquals(client1, client2)
880+
881+ def test_get_client_from_cache_with_purge(self):
882+ client1 = self.region.get_client(
883+ EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint,
884+ purge_cache=True)
885+ client2 = self.region.get_client(
886+ EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint,
887+ purge_cache=True)
888+ self.assertTrue(isinstance(client1, EC2Client))
889+ self.assertTrue(isinstance(client2, EC2Client))
890+ self.assertNotEquals(client1, client2)
891+
892+ def test_get_ec2_client_from_cache(self):
893+ client1 = self.region.get_ec2_client(self.creds)
894+ client2 = self.region.get_ec2_client(self.creds)
895+ self.assertEquals(self.creds, self.region.creds)
896+ self.assertTrue(isinstance(client1, EC2Client))
897+ self.assertTrue(isinstance(client2, EC2Client))
898+ self.assertEquals(client1, client2)
899+
900+
901+ def test_get_s3_client(self):
902+ self.assertRaises(NotImplementedError, self.region.get_s3_client)
903+
904+ def test_get_simpledb_client(self):
905+ self.assertRaises(NotImplementedError, self.region.get_simpledb_client)
906+
907+ def test_get_sqs_client(self):
908+ self.assertRaises(NotImplementedError, self.region.get_sqs_client)

Subscribers

People subscribed via source and target branches