Merge lp:~oubiwann/txaws/416109-arbitrary-endpoints into lp:~txawsteam/txaws/trunk
- 416109-arbitrary-endpoints
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
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 : Posted in a previous version of this proposal | # |
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
--
- 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.
Thomas Herve (therve) wrote : | # |
[1] Some flakes
txaws/ec2/
txaws/storage/
txaws/storage/
txaws/storage/
txaws/tests/
[2]
+ try:
+ gtk2reactor.
+ except AssertionError:
+ pass
Really? What's the reason behind this change?
[3]
key_id = content.
creds = AWSCredentials(
+ region = AWSServiceRegio
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.assertEqua
+
+
+ def test_get_
There is an extra blank line here.
Nice branch, +1!
Jamu Kakar (jkakar) wrote : | # |
[1]
+ def get_s3_
+ raise NotImplementedError
+
+ def get_simpledb_
+ raise NotImplementedError
+
+ def get_sqs_
+ 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.
+ return self.endpoint.
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.orig_environ = dict(os.environ)
+ self.addCleanup
+ if 'AWS_ACCESS_KEY_ID' in os.environ:
+ del os.environ[
+ if 'AWS_SECRET_
+ del os.environ[
+
+ def restore_
+ for key in set(os.environ) - set(self.
+ del os.environ[key]
+ os.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:/
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
+
+ def clean_environme
+ if os.environ.
+ del os.environ[
+ if os.environ.
+ del os.environ[
This code is unnecessary given the {stash,
in TXAWSTestCase.
Looks good, +1!
- 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).
Duncan McGreggor (oubiwann) wrote : | # |
> [1] Some flakes
> txaws/ec2/
> txaws/storage/
> txaws/storage/
> txaws/storage/
> txaws/tests/
Fixed.
> [2]
> + try:
> + gtk2reactor.
> + 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.
> secret_key =
> content.
> creds = AWSCredentials(
> + region = AWSServiceRegio
> self.create_
> gnomekeyring.
> 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.assertEqua
> +
> +
> + def test_get_
>
> 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
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) |
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).