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