Merge lp:~jimbaker/pyjuju/expose-provider-ec2 into lp:pyjuju
- expose-provider-ec2
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gustavo Niemeyer |
Approved revision: | 330 |
Merged at revision: | 309 |
Proposed branch: | lp:~jimbaker/pyjuju/expose-provider-ec2 |
Merge into: | lp:pyjuju |
Prerequisite: | lp:~jimbaker/pyjuju/expose-provision-machines-reexpose |
Diff against target: |
1247 lines (+566/-203) 23 files modified
ensemble/agents/provision.py (+8/-5) ensemble/agents/tests/test_provision.py (+1/-1) ensemble/agents/tests/test_unit.py (+1/-2) ensemble/agents/unit.py (+2/-1) ensemble/machine/__init__.py (+7/-6) ensemble/machine/tests/test_machine.py (+0/-2) ensemble/providers/common/launch.py (+22/-15) ensemble/providers/common/tests/test_launch.py (+2/-3) ensemble/providers/dummy.py (+3/-3) ensemble/providers/ec2/__init__.py (+14/-0) ensemble/providers/ec2/accessor.py (+19/-14) ensemble/providers/ec2/launch.py (+70/-78) ensemble/providers/ec2/securitygroup.py (+90/-0) ensemble/providers/ec2/tests/common.py (+42/-29) ensemble/providers/ec2/tests/test_accessor.py (+24/-2) ensemble/providers/ec2/tests/test_bootstrap.py (+12/-6) ensemble/providers/ec2/tests/test_files.py (+1/-1) ensemble/providers/ec2/tests/test_launch.py (+106/-25) ensemble/providers/ec2/tests/test_securitygroup.py (+129/-0) ensemble/providers/orchestra/launch.py (+1/-1) ensemble/providers/orchestra/tests/common.py (+0/-1) ensemble/providers/tests/test_dummy.py (+9/-8) examples/wordpress/hooks/db-relation-changed (+3/-0) |
To merge this branch: | bzr merge lp:~jimbaker/pyjuju/expose-provider-ec2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Approve | ||
William Reade (community) | Approve | ||
Review via email: mp+68478@code.launchpad.net |
Commit message
Description of the change
This branch implements the following:
* When launching a machine, it ensures the creation of an overall
Ensemble security group for all machines, with only port 22/tcp
open, named "ensemble-
security group named "ensemble-
ports open.
* The EC2 provider implementation of open_port, close_port, and
get_opened_ports corresponds to EC2 operations of
authorize_
describe_
IPPermissions model is parsed to return a set of (port, proto)
pairs for a given machine.
Lastly, this branch also adds this formula change to the wordpress
db-relation-changed hook. Originally this was going to be in a
separate branch, but this change makes it possible to really test!
# Make it publicly visible, once the wordpress service is exposed
open-port 80/tcp
Given the above, the branch can be readily tested by deploying the
wordpress and mysql example formulas as usual, then doing:
ensemble expose wordpress
This will result in debug log output like so:
2011-07-19 16:24:58,498 provision:ec2: ensemble.
2011-07-19 16:24:58,840 provision:ec2: ensemble.ec2 DEBUG: Opened 80/tcp on provider machine 'i-4bc10a2a'
Further operations of unexpose/expose are also supported, given the
dependence on lp:~jimbaker/ensemble/expose-provision-machines-reexpose
Gustavo Niemeyer (niemeyer) wrote : | # |
[1]
On an overlook, this feels nice. We just debated online about one detail that
would be good to get handled before this is merged: there's no reason to have
that logic inside classes. Let's talk further online if you want to debate
more on this.
Jim Baker (jimbaker) wrote : | # |
On Tue, Jul 26, 2011 at 9:19 AM, William Reade
<email address hidden>wrote:
> Review: Needs Fixing
> * Tiny, irrelevant note: rather than directly creating an
> EC2ProviderMachine, there's now a machine_
> ec2.machine which is a touch more convenient.
>
Now using
>
> * Significant issue: I don't like having both 'machine' and 'machine_id'
> parameters: if there isn't currently a way to determine machine_id given a
> machine, I think it would be worth adding one and using it, to avoid
> cluttering up the Provider interface.
>
Changed such that ProviderMachine now takes an optional machine_id so this
can be passed around. In general, this field needs to be assigned after the
fact (EC2 doesn't know these machine IDs from our topology), and this is
what is done in the provisioning agent. But it does provide a central place
for this information instead of having to pass around two related pieces.
Jim Baker (jimbaker) wrote : | # |
On Tue, Jul 26, 2011 at 9:51 AM, Gustavo Niemeyer <email address hidden>wrote:
> Review: Needs Fixing
> [1]
>
> On an overlook, this feels nice. We just debated online about one detail
> that
> would be good to get handled before this is merged: there's no reason to
> have
> that logic inside classes. Let's talk further online if you want to debate
> more on this.
>
Removed class wrappers in favor of just functions.
William Reade (fwereade) wrote : | # |
> Changed such that ProviderMachine now takes an optional machine_id so this
> can be passed around. In general, this field needs to be assigned after the
> fact (EC2 doesn't know these machine IDs from our topology), and this is
> what is done in the provisioning agent. But it does provide a central place
> for this information instead of having to pass around two related pieces.
Cool. One day, it would be good to ensure we do always have machine_id set on ProviderMachine instances, but no clean way to do so springs to mind now, so I don't think it blocks anything.
Everything else looks sensible to me, +1. (And I definitely like the operations as functions :))
Gustavo Niemeyer (niemeyer) wrote : | # |
Here are a few additional points Jim. Nothing huge, I think:
[1]
+ self._watched_
Bogus line. It's duplicated.
[2]
machine.
(...)
We really need to fix this. You ask the provider for a machine, it
gives you the machine with a field it has no idea about how to
retrieve, you stuff an id into that object, and then give it _back_
to the provider because it will need it.
Result:
- The provider is the one responsible for its machines, but can't
create one with the needed data.
- The provider expects a machine_id in arbitrary moments, and doesn't
tell when.
- Arbitrary locations set the id, because they magically know the dance.
No one but you understand that. We need a more consistent API to
handle this.
If get_open_ports and friends need unique identifier, let's provide it
explicitly. This should be the external id, not the internal id used
within zookeeper.
Unless the providers can set the machine_id attribute, please take it
off from the machine.
[3]
+ def __init__(self, instance_id, dns_name=None,
+ private_
Per above, please drop this.
[4]
- launch_deferred = self.get_
- launch_
+ machine_variables = yield self.get_
+ provider_machines = yield self.start_machine(
+ machine_variables, machine_data)
The idea of having a data bag like machine_data was a bad one. Now we
have logic all around which depends on arbitrary fields inside it, and
do not say so. Let's not increase the problem.
If start_machine needs the machine_id, let's just provide the machine_id
rather than a bag.
[5]
+ return open_provider_
Much better, thanks!
[6]
+ except EC2Error:
+ # AWS may return an error when the instance is not found
+ # (but presumably eventually will be), eg,
+ # txaws.ec2.
+ # The instance ID 'i-afe113ce' does not exist
+ raise ProviderInterac
+ "Machine (id: %r) was not found" % provider_id)
This looks too generic. What other errors can EC2Error represent?
Also, should we perhaps try again a couple of times before giving
up if the provider_id was indeed not found, to compensate for the
boredom of eventual consistency?
For the latter, free to just add a comment in case you don't want
to do now. The former looks like an issue we'll want to address
before merging.
[7]
- # TODO: For now authorize external traffic to the instance,
- # except for the ZooKeeper port. The expose functionality
Good to see that stuff going away.
[8]
+ if ensemble_
+ yield self._provider.
+ ensemble_
Has this logic ...
Jim Baker (jimbaker) wrote : | # |
On Fri, Jul 29, 2011 at 3:09 PM, Gustavo Niemeyer <email address hidden>wrote:
> Review: Needs Fixing
> Here are a few additional points Jim. Nothing huge, I think:
>
>
> [1]
>
> self._watched_
> + self._watched_
>
>
> Bogus line. It's duplicated.
>
Fixed
>
>
> [2]
>
> machine = yield self.provider.
> machine.machine_id = machine_id
> (...)
> current_ports = yield self.provider.
>
> We really need to fix this. You ask the provider for a machine, it
> gives you the machine with a field it has no idea about how to
> retrieve, you stuff an id into that object, and then give it _back_
> to the provider because it will need it.
>
> Result:
>
> - The provider is the one responsible for its machines, but can't
> create one with the needed data.
> - The provider expects a machine_id in arbitrary moments, and doesn't
> tell when.
> - Arbitrary locations set the id, because they magically know the dance.
>
> No one but you understand that. We need a more consistent API to
> handle this.
>
> If get_open_ports and friends need unique identifier, let's provide it
> explicitly. This should be the external id, not the internal id used
> within zookeeper.
>
> Unless the providers can set the machine_id attribute, please take it
> off from the machine.
>
Changed accordingly to a model where machine_id is passed explicitly.
Confirmed with William that this is OK.
>
>
> [3]
>
> + def __init__(self, instance_id, dns_name=None,
> + private_
> machine_id=None):
>
> Per above, please drop this.
>
Reverted
>
> [4]
>
> - launch_deferred = self.get_
> - launch_
> + machine_variables = yield self.get_
> + provider_machines = yield self.start_machine(
> + machine_variables, machine_data)
>
> The idea of having a data bag like machine_data was a bad one. Now we
> have logic all around which depends on arbitrary fields inside it, and
> do not say so. Let's not increase the problem.
>
> If start_machine needs the machine_id, let's just provide the machine_id
> rather than a bag.
>
Changed so that start_machine takes machine_id. Note there are two
start_machine functions, which is probably not ideal. I only refactored this
one.
>
> [5]
>
> + return open_provider_
>
> Much better, thanks!
>
Cool!
>
> [6]
>
> + except EC2Error:
> + # AWS may return an error when the instance is not found
> + # (but presumably eventually will be), eg,
> + # txaws.ec2.
> + # The instance ID 'i-afe113ce' does not exist
> + raise ProviderInterac
> + "Machine (id: %r) was not found" % provider_id)
>
> This looks too generic. What other errors can EC2Error represent?
>
Well, I have certainly seen them, but I don't know of a complete list. I
will look. I have made this and the similar u...
Gustavo Niemeyer (niemeyer) wrote : | # |
Looks good, +1 with a few extra comments:
[13]
+ raise ProviderInterac
+ "Got EC2 error (id %r: %s) when looking up machine" % (
+ provider_id, ex))
Please reword it like this:
"EC2 error when looking up instance %s: %s" % (provider_id, e)
[14]
+ raise ProviderInterac
+ "Machine (id: %r) was not found" % provider_id)
Please reword as:
"EC2 instance %s was not found"
[15]
+ except EC2Error:
+ log.debug("Ignoring machine group %s that just disappeared",
+ ensemble_
+ pass # Security group for machine is no longer around, ignore
The pass + comment may be dropped as it's redundant with the previous line.
[16]
+# TODO These security group functions do not handle the eventual
+# consistency seen with EC2. This is likely not the right place to
+# address this issue. The provisioning agent is more likely the place
+# to do this.
This would mean we're introducing eventual consistency within our API,
which sounds really bad to handle. The eventual consistency aspect
should be handled as closely to the EC2 API as possible, rather than
across all of the library.
Please adapt the comment to reflect this.
[17]
+ "Got EC2 error (id %r: %s) when attempting to open %s/%s" % (
+ machine.
"EC2 error when attempting to open %s/%s on machine %s: %s"
[18]
Similar as the above for the other messages.
[19]
+ for ip_permission in security_
+ if ip_permission.
Is it possible for this list to be empty? In which circumstances?
Do we have to handle it to avoid breaking the agent entirely?
[20]
+ if from_port == to_port:
+ # Ignore ranges of ports, since they are set outside of
+ # Ensemble (at this time at least)
This comment seems reversed. This branch of logic is the one that
does _not_ ignore. It should probably be reworded in the positive
side to be more clear ("Only take ...").
[21]
It would be awesome to take the chance we have Mark and Clint here
and take them through a whole run of that branch before merging,
both to share the knowledge and to help ensuring the latest changes
work fine.
- 331. By Jim Baker
-
Added logging on creating machine security group
- 332. By Jim Baker
-
Revised error messages
- 333. By Jim Baker
-
Add testing around a security group is still active and cannot be deleted
- 334. By Jim Baker
-
PEP8 & PyFlakes
- 335. By Jim Baker
-
Merged upstream & resolve conflicts
- 336. By Jim Baker
-
PEP8/PyFlakes/
leftover text conflict
Jim Baker (jimbaker) wrote : | # |
On Tue, Aug 9, 2011 at 11:41 AM, Gustavo Niemeyer <email address hidden>wrote:
> Review: Approve
> Looks good, +1 with a few extra comments:
>
> [13]
>
> + raise ProviderInterac
> + "Got EC2 error (id %r: %s) when looking up machine" % (
> + provider_id, ex))
>
> Please reword it like this:
>
> "EC2 error when looking up instance %s: %s" % (provider_id, e)
>
Done
>
>
> [14]
>
> + raise ProviderInterac
> + "Machine (id: %r) was not found" % provider_id)
>
> Please reword as:
>
> "EC2 instance %s was not found"
>
Done
>
> [15]
>
> + except EC2Error:
> + log.debug("Ignoring machine group %s that just
> disappeared",
> + ensemble_
> + pass # Security group for machine is no longer around,
> ignore
>
> The pass + comment may be dropped as it's redundant with the previous line.
>
Removed pass, but per conversation in sprint room, the logic was wrong, so
it nows raises this as ProviderInterac
almost certainly because of a security group that cannot be deleted because
it's used by another instance (generally being shut down). This will be
addressed in another branch that does security group cleanup on ensemble
shutdown.
>
> [16]
>
> +# TODO These security group functions do not handle the eventual
> +# consistency seen with EC2. This is likely not the right place to
> +# address this issue. The provisioning agent is more likely the place
> +# to do this.
>
> This would mean we're introducing eventual consistency within our API,
> which sounds really bad to handle. The eventual consistency aspect
> should be handled as closely to the EC2 API as possible, rather than
> across all of the library.
>
> Please adapt the comment to reflect this.
>
Fixed
>
> [17]
>
> + "Got EC2 error (id %r: %s) when attempting to open %s/%s" % (
> + machine.
>
> "EC2 error when attempting to open %s/%s on machine %s: %s"
>
Fixed
>
> [18]
>
> Similar as the above for the other messages.
>
Fixed
>
> [19]
>
> + for ip_permission in security_
> + if ip_permission.
>
> Is it possible for this list to be empty? In which circumstances?
> Do we have to handle it to avoid breaking the agent entirely?
>
security_groups will not be empty
>
> [20]
>
> + if from_port == to_port:
> + # Ignore ranges of ports, since they are set outside of
> + # Ensemble (at this time at least)
>
> This comment seems reversed. This branch of logic is the one that
> does _not_ ignore. It should probably be reworded in the positive
> side to be more clear ("Only take ...").
>
Changed
>
> [21]
>
> It would be awesome to take the chance we have Mark and Clint here
> and take them through a whole run of that branch before merging,
> both to share the knowledge and to help ensuring the latest changes
> work fine.
>
Mark and Jorge both ran this successfully (a reasonable sub for Clint).
Preview Diff
1 | === modified file 'ensemble/agents/provision.py' |
2 | --- ensemble/agents/provision.py 2011-08-10 18:22:54 +0000 |
3 | +++ ensemble/agents/provision.py 2011-08-10 18:22:55 +0000 |
4 | @@ -317,8 +317,10 @@ |
5 | yield self.open_close_ports(unit_state) |
6 | |
7 | if not exposed: |
8 | + log.debug("Service %r is unexposed", service_name) |
9 | self._watched_services[service_name] = NotExposed |
10 | else: |
11 | + log.debug("Service %r is exposed", service_name) |
12 | self._watched_services[service_name] = set() |
13 | yield self._setup_service_unit_watch(service_state) |
14 | |
15 | @@ -332,7 +334,7 @@ |
16 | @inlineCallbacks |
17 | def cb_check_service_units(old_service_units, new_service_units): |
18 | watched_units = self._watched_services.get( |
19 | - service_state.service_name) |
20 | + service_state.service_name, NotExposed) |
21 | if not self._running or watched_units is NotExposed: |
22 | raise StopWatcher() |
23 | |
24 | @@ -447,14 +449,15 @@ |
25 | for port in ports: |
26 | policy_ports.add( |
27 | (port["port"], port["proto"])) |
28 | - |
29 | - current_ports = yield self.provider.get_opened_ports(machine) |
30 | + current_ports = yield self.provider.get_opened_ports( |
31 | + machine, machine_id) |
32 | to_open = policy_ports - current_ports |
33 | to_close = current_ports - policy_ports |
34 | for port, proto in to_open: |
35 | - yield self.provider.open_port(machine, port, proto) |
36 | + yield self.provider.open_port(machine, machine_id, port, proto) |
37 | for port, proto in to_close: |
38 | - yield self.provider.close_port(machine, port, proto) |
39 | + yield self.provider.close_port( |
40 | + machine, machine_id, port, proto) |
41 | except ProviderInteractionError: |
42 | log.info("No provisioned machine for machine %r", machine_id) |
43 | finally: |
44 | |
45 | === modified file 'ensemble/agents/tests/test_provision.py' |
46 | --- ensemble/agents/tests/test_provision.py 2011-08-10 18:22:54 +0000 |
47 | +++ ensemble/agents/tests/test_provision.py 2011-08-10 18:22:55 +0000 |
48 | @@ -652,7 +652,7 @@ |
49 | instance_id = yield machine.get_instance_id() |
50 | machine_provider = yield self.agent.provider.get_machine(instance_id) |
51 | provider_ports = yield self.agent.provider.get_opened_ports( |
52 | - machine_provider) |
53 | + machine_provider, machine.id) |
54 | returnValue(provider_ports) |
55 | |
56 | def test_open_close_ports_on_machine(self): |
57 | |
58 | === modified file 'ensemble/agents/tests/test_unit.py' |
59 | --- ensemble/agents/tests/test_unit.py 2011-07-13 09:04:36 +0000 |
60 | +++ ensemble/agents/tests/test_unit.py 2011-08-10 18:22:55 +0000 |
61 | @@ -265,8 +265,7 @@ |
62 | "app-relation-changed", executor=self.agent.executor) |
63 | |
64 | # trigger the hook that will read service options |
65 | - wordpress_states = yield self.add_opposite_service_unit( |
66 | - self.states) |
67 | + yield self.add_opposite_service_unit(self.states) |
68 | |
69 | # Verify the hook has executed. |
70 | yield hook_complete |
71 | |
72 | === modified file 'ensemble/agents/unit.py' |
73 | --- ensemble/agents/unit.py 2011-07-21 01:15:02 +0000 |
74 | +++ ensemble/agents/unit.py 2011-08-10 18:22:55 +0000 |
75 | @@ -188,7 +188,8 @@ |
76 | log.debug("Configuration Changed") |
77 | |
78 | if current_state != "started": |
79 | - log.debug("Configuration updated on service in a non-started state") |
80 | + log.debug( |
81 | + "Configuration updated on service in a non-started state") |
82 | returnValue(None) |
83 | |
84 | yield self.workflow.fire_transition("reconfigure") |
85 | |
86 | === modified file 'ensemble/machine/__init__.py' |
87 | --- ensemble/machine/__init__.py 2011-07-13 22:08:49 +0000 |
88 | +++ ensemble/machine/__init__.py 2011-08-10 18:22:55 +0000 |
89 | @@ -2,14 +2,15 @@ |
90 | |
91 | class ProviderMachine(object): |
92 | """ |
93 | - Representative of a machine resource created by a C{MachineProvider}. The |
94 | - object is typically annotated by the machine provider, such that the |
95 | - provider can perform subsequent actions upon it, using the additional |
96 | - metadata for identification, without leaking these details to consumers of |
97 | - the C{MachineProvider} api. |
98 | + Representative of a machine resource created by a |
99 | + :class:`MachineProvider`. The object is typically annotated by the |
100 | + machine provider, such that the provider can perform subsequent |
101 | + actions upon it, using the additional metadata for identification, |
102 | + without leaking these details to consumers of the |
103 | + :class:`MachineProvider` api. |
104 | """ |
105 | |
106 | - def __init__(self, instance_id, dns_name=None, |
107 | + def __init__(self, instance_id, dns_name=None, |
108 | private_dns_name=None, launch_time=None): |
109 | self.instance_id = instance_id |
110 | # ideally this would be ip_address, but txaws doesn't expose it. |
111 | |
112 | === modified file 'ensemble/machine/tests/test_machine.py' |
113 | --- ensemble/machine/tests/test_machine.py 2011-01-26 23:39:35 +0000 |
114 | +++ ensemble/machine/tests/test_machine.py 2011-08-10 18:22:55 +0000 |
115 | @@ -1,6 +1,4 @@ |
116 | - |
117 | from ensemble.lib.testing import TestCase |
118 | - |
119 | from ensemble.machine import ProviderMachine |
120 | |
121 | |
122 | |
123 | === modified file 'ensemble/providers/common/launch.py' |
124 | --- ensemble/providers/common/launch.py 2011-08-03 14:48:50 +0000 |
125 | +++ ensemble/providers/common/launch.py 2011-08-10 18:22:55 +0000 |
126 | @@ -1,6 +1,6 @@ |
127 | import copy |
128 | |
129 | -from twisted.internet.defer import inlineCallbacks, returnValue, fail |
130 | +from twisted.internet.defer import inlineCallbacks, returnValue |
131 | |
132 | from ensemble.errors import ProviderError |
133 | from ensemble.state.auth import make_identity |
134 | @@ -69,31 +69,37 @@ |
135 | self._provider = provider |
136 | self._bootstrap = bootstrap |
137 | |
138 | + @inlineCallbacks |
139 | def run(self, machine_data): |
140 | """Launch an instance node within the machine provider environment. |
141 | |
142 | - @param machine_data a dictionary of data that is passed along to |
143 | - provided to the machine as serialized yaml. 'machine-id' is a |
144 | - required key, denoting the machine's zookeeper node for its |
145 | - machine agent. |
146 | + `machine_data`: a dictionary of data that is passed along to |
147 | + provided to the machine as serialized yaml. 'machine-id' is a |
148 | + required key, denoting the machine's zookeeper node for its |
149 | + machine agent. |
150 | """ |
151 | if "machine-id" not in machine_data: |
152 | - return fail(ProviderError( |
153 | - "Machine state `machine-id` not provided in machine_data.")) |
154 | + raise ProviderError( |
155 | + "Machine state `machine-id` not provided in machine_data.") |
156 | |
157 | - launch_deferred = self.get_machine_variables(machine_data) |
158 | - launch_deferred.addCallback(self.start_machine) |
159 | + machine_variables = yield self.get_machine_variables(machine_data) |
160 | + provider_machines = yield self.start_machine( |
161 | + machine_variables, machine_data["machine-id"]) |
162 | if self._bootstrap: |
163 | - launch_deferred.addCallback(self._on_bootstrap_launched) |
164 | - return launch_deferred |
165 | + yield self._on_bootstrap_launched(provider_machines) |
166 | + returnValue(provider_machines) |
167 | |
168 | - def start_machine(self, variables): |
169 | + def start_machine(self, machine_variables, machine_id): |
170 | """Actually launch a machine for the appropriate provider. |
171 | |
172 | - @param variables: non-provider-specific data, sufficient to |
173 | + `machine_variables`: non-provider-specific data, sufficient to |
174 | define the machine's behaviour once it exists. |
175 | |
176 | - @return: a list of newly-launched ProviderMachines. |
177 | + `machine_id`: the external machine ID (also exists in the |
178 | + above bag of data) |
179 | + |
180 | + Returns a singe-entry list containing a ProviderMachine for the |
181 | + new instance |
182 | """ |
183 | raise NotImplementedError() |
184 | |
185 | @@ -169,7 +175,8 @@ |
186 | return [ |
187 | "sudo apt-get install -y python-txzookeeper", |
188 | "sudo mkdir -p /usr/lib/ensemble", |
189 | - "cd /usr/lib/ensemble && sudo /usr/bin/bzr co %s ensemble" % branch, |
190 | + "cd /usr/lib/ensemble && sudo /usr/bin/bzr co %s ensemble" % \ |
191 | + branch, |
192 | "cd /usr/lib/ensemble/ensemble && sudo python setup.py develop"] |
193 | |
194 | def _get_initialize_script(self): |
195 | |
196 | === modified file 'ensemble/providers/common/tests/test_launch.py' |
197 | --- ensemble/providers/common/tests/test_launch.py 2011-08-04 09:50:57 +0000 |
198 | +++ ensemble/providers/common/tests/test_launch.py 2011-08-10 18:22:55 +0000 |
199 | @@ -1,10 +1,9 @@ |
200 | -from cStringIO import StringIO |
201 | import logging |
202 | import tempfile |
203 | |
204 | from twisted.internet.defer import fail, succeed |
205 | |
206 | -from ensemble.errors import EnvironmentNotFound, FileNotFound, ProviderError |
207 | +from ensemble.errors import EnvironmentNotFound, ProviderError |
208 | from ensemble.lib.testing import TestCase |
209 | from ensemble.providers.common.bootstrap import Bootstrap |
210 | from ensemble.providers.common.launch import ( |
211 | @@ -18,7 +17,7 @@ |
212 | |
213 | class DummyLaunchMachine(LaunchMachine): |
214 | |
215 | - def start_machine(self, variables): |
216 | + def start_machine(self, variables, data): |
217 | if self._bootstrap: |
218 | name = "bootstrapped-instance-id" |
219 | else: |
220 | |
221 | === modified file 'ensemble/providers/dummy.py' |
222 | --- ensemble/providers/dummy.py 2011-08-10 18:22:54 +0000 |
223 | +++ ensemble/providers/dummy.py 2011-08-10 18:22:55 +0000 |
224 | @@ -122,7 +122,7 @@ |
225 | config["dynamicduck"] = "magic" |
226 | return config |
227 | |
228 | - def open_port(self, machine, port, protocol="tcp"): |
229 | + def open_port(self, machine, machine_id, port, protocol="tcp"): |
230 | """Dummy equivalent of ec2-authorize-group""" |
231 | if not isinstance(machine, DummyMachine): |
232 | return fail(ProviderError("Invalid machine for provider")) |
233 | @@ -131,7 +131,7 @@ |
234 | port, protocol, machine.instance_id) |
235 | return succeed(None) |
236 | |
237 | - def close_port(self, machine, port, protocol="tcp"): |
238 | + def close_port(self, machine, machin_id, port, protocol="tcp"): |
239 | """Dummy equivalent of ec2-revoke-group""" |
240 | if not isinstance(machine, DummyMachine): |
241 | return fail(ProviderError("Invalid machine for provider")) |
242 | @@ -143,7 +143,7 @@ |
243 | pass |
244 | return succeed(None) |
245 | |
246 | - def get_opened_ports(self, machine): |
247 | + def get_opened_ports(self, machine, machine_id): |
248 | """Dummy equivalent of ec2-describe-group |
249 | |
250 | This returns the current exposed ports in the environment for |
251 | |
252 | === modified file 'ensemble/providers/ec2/__init__.py' |
253 | --- ensemble/providers/ec2/__init__.py 2011-08-05 12:43:39 +0000 |
254 | +++ ensemble/providers/ec2/__init__.py 2011-08-10 18:22:55 +0000 |
255 | @@ -16,6 +16,8 @@ |
256 | from .iterate import EC2MachineIteration |
257 | from .launch import EC2LaunchMachine |
258 | from .machine import get_machine |
259 | +from .securitygroup import ( |
260 | + open_provider_port, close_provider_port, get_provider_opened_ports) |
261 | from .shutdown import EC2Shutdown, EC2ShutdownMachine |
262 | from .utils import get_region_uri |
263 | |
264 | @@ -147,3 +149,15 @@ |
265 | @raise: EnvironmentNotFound or EnvironmentPending |
266 | """ |
267 | return find_zookeepers(self, get_machine) |
268 | + |
269 | + def open_port(self, machine, machine_id, port, protocol="tcp"): |
270 | + """Authorizes `port` using `protocol` on EC2 for `machine`.""" |
271 | + return open_provider_port(self, machine, machine_id, port, protocol) |
272 | + |
273 | + def close_port(self, machine, machine_id, port, protocol="tcp"): |
274 | + """Revokes `port` using `protocol` on EC2 for `machine`.""" |
275 | + return close_provider_port(self, machine, machine_id, port, protocol) |
276 | + |
277 | + def get_opened_ports(self, machine, machine_id): |
278 | + """Returns a set of open (port, proto) pairs for `machine`.""" |
279 | + return get_provider_opened_ports(self, machine, machine_id) |
280 | |
281 | === modified file 'ensemble/providers/ec2/accessor.py' |
282 | --- ensemble/providers/ec2/accessor.py 2011-07-13 22:08:49 +0000 |
283 | +++ ensemble/providers/ec2/accessor.py 2011-08-10 18:22:55 +0000 |
284 | @@ -1,4 +1,5 @@ |
285 | -from twisted.internet.defer import fail |
286 | +from twisted.internet.defer import inlineCallbacks, returnValue |
287 | +from txaws.ec2.exception import EC2Error |
288 | |
289 | from ensemble.errors import ProviderInteractionError |
290 | |
291 | @@ -9,17 +10,21 @@ |
292 | class EC2MachineAccessor(EC2ProviderMachineFilter): |
293 | """Retrieve a specific machine by provider id.""" |
294 | |
295 | + @inlineCallbacks |
296 | def run(self, provider_id): |
297 | - d = self._provider.ec2.describe_instances(provider_id) |
298 | - d.addCallback(self._filter_provider_machines) |
299 | - d.addCallback(self._create_provider_machine, provider_id) |
300 | - return d |
301 | - |
302 | - def _create_provider_machine(self, instances, provider_id): |
303 | - if not instances: |
304 | - return fail( |
305 | - ProviderInteractionError( |
306 | - "Machine (id: %r) was not found" % provider_id)) |
307 | - |
308 | - instance = instances.pop() |
309 | - return machine_from_instance(instance) |
310 | + try: |
311 | + instances = yield self._provider.ec2.describe_instances( |
312 | + provider_id) |
313 | + except EC2Error, e: |
314 | + # AWS may return an error when the instance is not found |
315 | + # (but presumably eventually will be), eg, |
316 | + # txaws.ec2.exception.EC2Error: Error Message: |
317 | + # The instance ID 'i-afe113ce' does not exist |
318 | + raise ProviderInteractionError( |
319 | + "EC2 error when looking up instance %s: %s" % (provider_id, e)) |
320 | + filtered = self._filter_provider_machines(instances) |
321 | + if not filtered: |
322 | + raise ProviderInteractionError( |
323 | + "EC2 instance %s was not found" % provider_id) |
324 | + instance = filtered.pop() |
325 | + returnValue(machine_from_instance(instance)) |
326 | |
327 | === modified file 'ensemble/providers/ec2/launch.py' |
328 | --- ensemble/providers/ec2/launch.py 2011-07-18 07:07:03 +0000 |
329 | +++ ensemble/providers/ec2/launch.py 2011-08-10 18:22:55 +0000 |
330 | @@ -1,5 +1,7 @@ |
331 | from twisted.internet.defer import inlineCallbacks, returnValue |
332 | +from txaws.ec2.exception import EC2Error |
333 | |
334 | +from ensemble.errors import ProviderInteractionError |
335 | from ensemble.providers.common.launch import LaunchMachine |
336 | |
337 | from .machine import machine_from_instance |
338 | @@ -10,26 +12,30 @@ |
339 | """Amazon EC2 operation for launching an instance""" |
340 | |
341 | @inlineCallbacks |
342 | - def start_machine(self, variables): |
343 | + def start_machine(self, machine_variables, machine_id): |
344 | """Actually launch an instance on EC2. |
345 | |
346 | - @param variables: should be a dictionary with entries sufficient |
347 | - to specify everything necessary to launch the requested |
348 | - instance. |
349 | - |
350 | - @return: a singe-entry list containing a ProviderMachine for the |
351 | - new instance |
352 | + `machine_variables`: non-provider-specific data, sufficient to |
353 | + define the machine's behaviour once it exists. |
354 | + |
355 | + `machine_id`: the external machine ID (also exists in the |
356 | + above bag of data) |
357 | + |
358 | + Returns a singe-entry list containing a ProviderMachine for the |
359 | + new instance |
360 | """ |
361 | # Retrieves standard ec2 run_instances arguments, of note |
362 | # image id, and instance type. |
363 | machine_options = yield get_launch_options( |
364 | self._provider.config, |
365 | - **variables) |
366 | + **machine_variables) |
367 | |
368 | - # Ensure the ensemble security group exists and is included in the |
369 | - # machine instance launch options. |
370 | + # Ensure the ensemble security groups exist (both the overall |
371 | + # group and the machine-specific group) and are included in |
372 | + # the machine instance launch options. |
373 | machine_options["security_groups"] = yield self._ensure_group( |
374 | - machine_options["security_groups"]) |
375 | + machine_options["security_groups"], |
376 | + machine_id) |
377 | |
378 | # Launch the instance. |
379 | instances = yield self._provider.ec2.run_instances(**machine_options) |
380 | @@ -54,24 +60,26 @@ |
381 | vars_deferred.addCallback(set_provider_type) |
382 | return vars_deferred |
383 | |
384 | - def _ensure_group(self, groups): |
385 | + @inlineCallbacks |
386 | + def _ensure_group(self, groups, machine_id): |
387 | """Ensure the ensemble group is the machine launch groups. |
388 | |
389 | - Machines launched by ensemble are tagged with a group so they can |
390 | - be distinguished from other machines that might be running on |
391 | - an EC2 account. This group can be specified explicitly or implicitly |
392 | - defined by the environment name. |
393 | - |
394 | - @param security_groups: The configured security groups for a machine |
395 | - instance. |
396 | + Machines launched by ensemble are tagged with a group so they |
397 | + can be distinguished from other machines that might be running |
398 | + on an EC2 account. This group can be specified explicitly or |
399 | + implicitly defined by the environment name. In addition, a |
400 | + specific machine security group is created for each machine, |
401 | + so that its firewall rules can be configured per machine. |
402 | + |
403 | + `groups`: The configured security groups for a machine instance. |
404 | + |
405 | + `machine_id`: The external machine ID of this machine instance |
406 | """ |
407 | # Label the instance with the ensemble name. |
408 | - d = self._provider.ec2.describe_security_groups() |
409 | - d.addCallback(self._on_received_groups, groups) |
410 | - return d |
411 | - |
412 | - def _on_received_groups(self, security_groups, groups): |
413 | + security_groups = yield self._provider.ec2.describe_security_groups() |
414 | ensemble_group = "ensemble-%s" % self._provider.environment_name |
415 | + ensemble_machine_group = "ensemble-%s-%s" % ( |
416 | + self._provider.environment_name, machine_id) |
417 | group_ids = [group.name for group in security_groups] |
418 | |
419 | # Ensure the provider group is added to the instance groups. |
420 | @@ -81,65 +89,49 @@ |
421 | # Create the provider group if doesn't exist. |
422 | if not ensemble_group in group_ids: |
423 | log.debug("Creating ensemble provider group %s", ensemble_group) |
424 | - group_created = self._provider.ec2.create_security_group( |
425 | + yield self._provider.ec2.create_security_group( |
426 | ensemble_group, |
427 | "Ensemble group for %s" % self._provider.environment_name) |
428 | |
429 | # Authorize SSH. |
430 | - group_created.addCallback( |
431 | - lambda x: self._provider.ec2.authorize_security_group( |
432 | - ensemble_group, |
433 | - ip_protocol="tcp", |
434 | - from_port="22", to_port="22", |
435 | - cidr_ip="0.0.0.0/0")) |
436 | + yield self._provider.ec2.authorize_security_group( |
437 | + ensemble_group, |
438 | + ip_protocol="tcp", |
439 | + from_port="22", to_port="22", |
440 | + cidr_ip="0.0.0.0/0") |
441 | |
442 | # We need to describe the group to pickup the owner_id for auth. |
443 | - group_created.addCallback( |
444 | - lambda x: self._provider.ec2.describe_security_groups( |
445 | - ensemble_group)) |
446 | + groups_info = yield self._provider.ec2.describe_security_groups( |
447 | + ensemble_group) |
448 | |
449 | # Authorize Internal ZK Traffic |
450 | - group_created.addCallback( |
451 | - lambda groups: self._provider.ec2.authorize_security_group( |
452 | - ensemble_group, |
453 | - source_group_name=ensemble_group, |
454 | - source_group_owner_id=groups.pop().owner_id)) |
455 | - |
456 | - # TODO: For now authorize external traffic to the instance, |
457 | - # except for the ZooKeeper port. The expose functionality |
458 | - # that is in progress will address this in a better way. |
459 | - group_created.addCallback( |
460 | - lambda groups: self._provider.ec2.authorize_security_group( |
461 | - ensemble_group, |
462 | - ip_protocol="tcp", |
463 | - from_port="1", |
464 | - to_port="2180", |
465 | - cidr_ip="0.0.0.0/0")) |
466 | - group_created.addCallback( |
467 | - lambda groups: self._provider.ec2.authorize_security_group( |
468 | - ensemble_group, |
469 | - ip_protocol="tcp", |
470 | - from_port="2182", |
471 | - to_port="65535", |
472 | - cidr_ip="0.0.0.0/0")) |
473 | - |
474 | - group_created.addCallback( |
475 | - lambda groups: self._provider.ec2.authorize_security_group( |
476 | - ensemble_group, |
477 | - ip_protocol="udp", |
478 | - from_port="1", |
479 | - to_port="2180", |
480 | - cidr_ip="0.0.0.0/0")) |
481 | - group_created.addCallback( |
482 | - lambda groups: self._provider.ec2.authorize_security_group( |
483 | - ensemble_group, |
484 | - ip_protocol="udp", |
485 | - from_port="2182", |
486 | - to_port="65535", |
487 | - cidr_ip="0.0.0.0/0")) |
488 | - |
489 | - # We use a separate callback here, because we need to wait |
490 | - # on the authorize security group deferred to succeed before |
491 | - # we can return the groups result. |
492 | - return group_created.addCallback(lambda x: groups) |
493 | - return groups |
494 | + yield self._provider.ec2.authorize_security_group( |
495 | + ensemble_group, |
496 | + source_group_name=ensemble_group, |
497 | + source_group_owner_id=groups_info.pop().owner_id) |
498 | + |
499 | + # Create the machine-specific group, but first see if there's |
500 | + # one already existing from a previous machine launch; |
501 | + # if so, delete it, since it can have the wrong firewall setup |
502 | + if ensemble_machine_group in group_ids: |
503 | + try: |
504 | + yield self._provider.ec2.delete_security_group( |
505 | + ensemble_machine_group) |
506 | + log.debug("Deleted existing machine group %s, will replace", |
507 | + ensemble_machine_group) |
508 | + except EC2Error, e: |
509 | + log.debug("Cannot delete security group %s: %s", |
510 | + ensemble_machine_group, e) |
511 | + raise ProviderInteractionError( |
512 | + "EC2 error when attempting to delete " |
513 | + "security group %s: %s" % ( |
514 | + ensemble_machine_group, e)) |
515 | + log.debug("Creating ensemble machine security group %s", |
516 | + ensemble_machine_group) |
517 | + yield self._provider.ec2.create_security_group( |
518 | + ensemble_machine_group, |
519 | + "Ensemble group for %s machine %s" % ( |
520 | + self._provider.environment_name, machine_id)) |
521 | + groups.append(ensemble_machine_group) |
522 | + |
523 | + returnValue(groups) |
524 | |
525 | === added file 'ensemble/providers/ec2/securitygroup.py' |
526 | --- ensemble/providers/ec2/securitygroup.py 1970-01-01 00:00:00 +0000 |
527 | +++ ensemble/providers/ec2/securitygroup.py 2011-08-10 18:22:55 +0000 |
528 | @@ -0,0 +1,90 @@ |
529 | +from twisted.internet.defer import inlineCallbacks, returnValue |
530 | +from txaws.ec2.exception import EC2Error |
531 | + |
532 | +from ensemble.errors import ProviderInteractionError |
533 | + |
534 | +from .utils import log |
535 | + |
536 | + |
537 | +def _get_machine_group_name(provider, machine_id): |
538 | + """Get EC2 security group name associated just with `machine_id`.""" |
539 | + return "ensemble-%s-%s" % (provider.environment_name, machine_id) |
540 | + |
541 | + |
542 | +# TODO These security group functions do not handle the eventual |
543 | +# consistency seen with EC2. A future branch will add support for |
544 | +# retry so that using code doesn't have to be aware of this issue. |
545 | +# |
546 | +# In addition, the functions work with respect to the machine id, |
547 | +# since they manipulate a security group permanently associated with |
548 | +# the EC2 provided machine, and the machine must be launched into this |
549 | +# security group. This security group, per the above |
550 | +# `_get_machine_group_name`, embeds the machine id, eg |
551 | +# ensemble-moon-42. Ideally, this would not be the case. See the |
552 | +# comments associated with the merge proposal of |
553 | +# https://code.launchpad.net/~jimbaker/ensemble/expose-provider-ec2/ |
554 | + |
555 | + |
556 | +@inlineCallbacks |
557 | +def open_provider_port(provider, machine, machine_id, port, protocol): |
558 | + """Authorize `port`/`proto` for the machine security group.""" |
559 | + try: |
560 | + yield provider.ec2.authorize_security_group( |
561 | + _get_machine_group_name(provider, machine_id), |
562 | + ip_protocol=protocol, |
563 | + from_port=str(port), to_port=str(port), |
564 | + cidr_ip="0.0.0.0/0") |
565 | + log.debug("Opened %s/%s on provider machine %r", |
566 | + port, protocol, machine.instance_id) |
567 | + except EC2Error, e: |
568 | + raise ProviderInteractionError( |
569 | + "EC2 error when attempting to open %s/%s on machine %s: %s" % ( |
570 | + port, protocol, machine.instance_id, e)) |
571 | + |
572 | + |
573 | +@inlineCallbacks |
574 | +def close_provider_port(provider, machine, machine_id, port, protocol): |
575 | + """Revoke `port`/`proto` for the machine security group.""" |
576 | + try: |
577 | + yield provider.ec2.revoke_security_group( |
578 | + _get_machine_group_name(provider, machine_id), |
579 | + ip_protocol=protocol, |
580 | + from_port=str(port), to_port=str(port), |
581 | + cidr_ip="0.0.0.0/0") |
582 | + log.debug("Closed %s/%s on provider machine %r", |
583 | + port, protocol, machine.instance_id) |
584 | + except EC2Error, e: |
585 | + raise ProviderInteractionError( |
586 | + "EC2 error when attempting to close %s/%s on machine %s: %s" % ( |
587 | + port, protocol, machine.instance_id, e)) |
588 | + |
589 | + |
590 | +@inlineCallbacks |
591 | +def get_provider_opened_ports(provider, machine, machine_id): |
592 | + """Gets the opened ports for `machine`. |
593 | + |
594 | + Retrieves the IP permissions associated with the machine |
595 | + security group, then parses them to return a set of (port, |
596 | + proto) pairs. |
597 | + """ |
598 | + try: |
599 | + security_groups = yield provider.ec2.describe_security_groups( |
600 | + _get_machine_group_name(provider, machine_id)) |
601 | + except EC2Error, e: |
602 | + raise ProviderInteractionError( |
603 | + "EC2 error when attempting to get opened ports " |
604 | + "on machine %s: %s" % ( |
605 | + machine.instance_id, e)) |
606 | + |
607 | + opened_ports = set() # made up of (port, protocol) pairs |
608 | + for ip_permission in security_groups[0].allowed_ips: |
609 | + if ip_permission.cidr_ip != "0.0.0.0/0": |
610 | + continue |
611 | + from_port = int(ip_permission.from_port) |
612 | + to_port = int(ip_permission.to_port) |
613 | + if from_port == to_port: |
614 | + # Only return ports that are individually opened. We |
615 | + # ignore multi-port ranges, since they are set outside of |
616 | + # Ensemble (at this time at least) |
617 | + opened_ports.add((from_port, ip_permission.ip_protocol)) |
618 | + returnValue(opened_ports) |
619 | |
620 | === modified file 'ensemble/providers/ec2/tests/common.py' |
621 | --- ensemble/providers/ec2/tests/common.py 2011-07-27 10:35:59 +0000 |
622 | +++ ensemble/providers/ec2/tests/common.py 2011-08-10 18:22:55 +0000 |
623 | @@ -5,6 +5,7 @@ |
624 | from txaws.s3.client import S3Client |
625 | from txaws.s3.exception import S3Error |
626 | from txaws.ec2.client import EC2Client |
627 | +from txaws.ec2.exception import EC2Error |
628 | from txaws.ec2.model import Instance, SecurityGroup |
629 | |
630 | from ensemble.lib.mocker import KWARGS, MATCH |
631 | @@ -32,6 +33,29 @@ |
632 | """ |
633 | return MachineProvider(self.env_name, self.get_config()) |
634 | |
635 | + def get_ec2_error(self, entity_id, |
636 | + format="The instance ID %r does not exist", |
637 | + code=503): |
638 | + """Make a representative EC2Error for `entity_id`, eg AWS instance_id. |
639 | + |
640 | + This error is paired with `get_wrapped_ec2_text` below. The |
641 | + default format represents a fairly common error seen in |
642 | + working with EC2. There are others.""" |
643 | + message = format % entity_id |
644 | + return EC2Error( |
645 | + "<error><Code>1</Code><Message>%s</Message></error>" % message, |
646 | + code) |
647 | + |
648 | + def get_wrapped_ec2_error_text(self, entity_id, reason, |
649 | + format="The instance ID %r does not exist"): |
650 | + """By convention, `EC2Error` is wrapped as a `ProviderError`""" |
651 | + message = format % entity_id |
652 | + return ( |
653 | + "ProviderError: Interaction with machine provider failed: " |
654 | + "\"EC2 error when attempting to %s %s: " |
655 | + "Error Message: %s\"" % ( |
656 | + reason, entity_id, message)) |
657 | + |
658 | def setUp(self): |
659 | # mock out the aws services |
660 | service_factory = self.mocker.replace( |
661 | @@ -95,33 +119,24 @@ |
662 | source_group_owner_id="123") |
663 | self.mocker.result(succeed(True)) |
664 | |
665 | - self.ec2.authorize_security_group( |
666 | - group_name, |
667 | - ip_protocol="tcp", |
668 | - from_port="1", |
669 | - to_port="2180", |
670 | - cidr_ip="0.0.0.0/0") |
671 | - self.ec2.authorize_security_group( |
672 | - group_name, |
673 | - ip_protocol="tcp", |
674 | - from_port="2182", |
675 | - to_port="65535", |
676 | - cidr_ip="0.0.0.0/0") |
677 | - |
678 | - self.ec2.authorize_security_group( |
679 | - group_name, |
680 | - ip_protocol="udp", |
681 | - from_port="1", |
682 | - to_port="2180", |
683 | - cidr_ip="0.0.0.0/0") |
684 | - self.ec2.authorize_security_group( |
685 | - group_name, |
686 | - ip_protocol="udp", |
687 | - from_port="2182", |
688 | - to_port="65535", |
689 | - cidr_ip="0.0.0.0/0") |
690 | - |
691 | - self.mocker.result(succeed(True)) |
692 | + def _mock_create_machine_group(self, machine_id): |
693 | + machine_group_name = "ensemble-%s-%s" % (self.env_name, machine_id) |
694 | + self.ec2.create_security_group( |
695 | + machine_group_name, "Ensemble group for %s machine %s" % ( |
696 | + self.env_name, machine_id)) |
697 | + self.mocker.result(succeed(True)) |
698 | + |
699 | + def _mock_delete_machine_group(self, machine_id): |
700 | + machine_group_name = "ensemble-%s-%s" % (self.env_name, machine_id) |
701 | + self.ec2.delete_security_group(machine_group_name) |
702 | + self.mocker.result(succeed(True)) |
703 | + |
704 | + def _mock_delete_machine_group_was_deleted(self, machine_id): |
705 | + machine_group_name = "ensemble-%s-%s" % (self.env_name, machine_id) |
706 | + self.ec2.delete_security_group(machine_group_name) |
707 | + self.mocker.result(fail(self.get_ec2_error( |
708 | + machine_group_name, |
709 | + "There are active instances using security group %r"))) |
710 | |
711 | def _mock_get_zookeeper_hosts(self, hosts=None): |
712 | """ |
713 | @@ -152,5 +167,3 @@ |
714 | # connect grabs the first host of a set. |
715 | self.ec2.describe_instances(hosts[0].instance_id) |
716 | self.mocker.result(succeed([hosts[0]])) |
717 | - |
718 | - |
719 | |
720 | === modified file 'ensemble/providers/ec2/tests/test_accessor.py' |
721 | --- ensemble/providers/ec2/tests/test_accessor.py 2011-01-26 23:39:35 +0000 |
722 | +++ ensemble/providers/ec2/tests/test_accessor.py 2011-08-10 18:22:55 +0000 |
723 | @@ -1,4 +1,4 @@ |
724 | -from twisted.internet.defer import succeed |
725 | +from twisted.internet.defer import fail, succeed |
726 | from txaws.ec2.model import Instance, Reservation |
727 | |
728 | from ensemble.errors import ProviderInteractionError |
729 | @@ -54,7 +54,29 @@ |
730 | |
731 | def validate_error(error): |
732 | self.assertIn( |
733 | - "Machine (id: 'i-foobar') was not found", |
734 | + "EC2 instance i-foobar was not found", |
735 | + str(error)) |
736 | + |
737 | + d.addCallback(validate_error) |
738 | + return d |
739 | + |
740 | + def test_instance_not_found(self): |
741 | + """Verify that if the instance is not known to EC2, |
742 | + raises ProviderInteractionError. |
743 | + """ |
744 | + self.ec2.describe_instances("i-foobar") |
745 | + self.mocker.result(fail(self.get_ec2_error("i-foobar"))) |
746 | + self.mocker.replay() |
747 | + |
748 | + provider = self.get_provider() |
749 | + |
750 | + d = self.assertFailure( |
751 | + provider.get_machine("i-foobar"), |
752 | + ProviderInteractionError) |
753 | + |
754 | + def validate_error(error): |
755 | + self.assertIn( |
756 | + "The instance ID \'i-foobar\' does not exist", |
757 | str(error)) |
758 | |
759 | d.addCallback(validate_error) |
760 | |
761 | === modified file 'ensemble/providers/ec2/tests/test_bootstrap.py' |
762 | --- ensemble/providers/ec2/tests/test_bootstrap.py 2011-08-04 09:50:57 +0000 |
763 | +++ ensemble/providers/ec2/tests/test_bootstrap.py 2011-08-10 18:22:55 +0000 |
764 | @@ -35,7 +35,7 @@ |
765 | MATCH(match_string)) |
766 | self.mocker.result(succeed(True)) |
767 | |
768 | - def _mock_launch(self): |
769 | + def _mock_launch(self, machine_id): |
770 | """Mock launching a bootstrap machine on ec2.""" |
771 | credentials = "admin:%s" % self.get_config()["admin-secret"] |
772 | admin_identity = make_identity(credentials) |
773 | @@ -81,7 +81,9 @@ |
774 | instance_type="m1.small", |
775 | max_count=1, |
776 | min_count=1, |
777 | - security_groups=["ensemble-%s" % self.env_name], |
778 | + security_groups=[ |
779 | + "%s-%s" % ("ensemble", self.env_name), |
780 | + "%s-%s-%s" % ("ensemble", self.env_name, machine_id)], |
781 | user_data=MATCH(verify_user_data)) |
782 | |
783 | def test_launch_bootstrap(self): |
784 | @@ -95,8 +97,9 @@ |
785 | self.ec2.describe_security_groups() |
786 | self.mocker.result(succeed([])) |
787 | self._mock_create_group() |
788 | + self._mock_create_machine_group(0) |
789 | self._mock_launch_utils(region="us-east-1") |
790 | - self._mock_launch() |
791 | + self._mock_launch(0) |
792 | self.mocker.result(succeed([])) |
793 | self._mock_save() |
794 | self.mocker.replay() |
795 | @@ -124,8 +127,9 @@ |
796 | self.ec2.describe_security_groups() |
797 | self.mocker.result(succeed([ |
798 | SecurityGroup("ensemble-%s" % self.env_name, "")])) |
799 | + self._mock_create_machine_group(0) |
800 | self._mock_launch_utils(region="us-east-1") |
801 | - self._mock_launch() |
802 | + self._mock_launch(0) |
803 | self.mocker.result(succeed([])) |
804 | self._mock_save() |
805 | self.mocker.replay() |
806 | @@ -201,8 +205,9 @@ |
807 | self.ec2.describe_security_groups() |
808 | self.mocker.result(succeed([ |
809 | SecurityGroup("ensemble-%s" % self.env_name, "")])) |
810 | + self._mock_create_machine_group(0) |
811 | self._mock_launch_utils(region="us-east-1") |
812 | - self._mock_launch() |
813 | + self._mock_launch(0) |
814 | self.mocker.result(succeed(instances)) |
815 | self._mock_save() |
816 | self.mocker.replay() |
817 | @@ -229,8 +234,9 @@ |
818 | self.ec2.describe_security_groups() |
819 | self.mocker.result(succeed([ |
820 | SecurityGroup("ensemble-%s" % self.env_name, "")])) |
821 | + self._mock_create_machine_group(0) |
822 | self._mock_launch_utils(region="us-east-1") |
823 | - self._mock_launch() |
824 | + self._mock_launch(0) |
825 | self.mocker.result(succeed(instances)) |
826 | self._mock_save() |
827 | self.mocker.replay() |
828 | |
829 | === modified file 'ensemble/providers/ec2/tests/test_files.py' |
830 | --- ensemble/providers/ec2/tests/test_files.py 2011-08-01 20:25:56 +0000 |
831 | +++ ensemble/providers/ec2/tests/test_files.py 2011-08-10 18:22:55 +0000 |
832 | @@ -100,7 +100,7 @@ |
833 | |
834 | def test_put_file_slightly_wrong_error(self): |
835 | error = S3Error("<ignored/>", 404) |
836 | - error.errors = [{"Code": "NoSuchKey"}] # note total nonsense |
837 | + error.errors = [{"Code": "NoSuchKey"}] # note total nonsense |
838 | return self.verify_strange_put_error(error) |
839 | |
840 | def test_put_file_unknown_error(self): |
841 | |
842 | === modified file 'ensemble/providers/ec2/tests/test_launch.py' |
843 | --- ensemble/providers/ec2/tests/test_launch.py 2011-08-02 09:15:47 +0000 |
844 | +++ ensemble/providers/ec2/tests/test_launch.py 2011-08-10 18:22:55 +0000 |
845 | @@ -1,10 +1,10 @@ |
846 | from yaml import load |
847 | |
848 | -from twisted.internet.defer import succeed |
849 | - |
850 | -from txaws.ec2.model import Instance |
851 | - |
852 | -from ensemble.errors import EnvironmentNotFound |
853 | +from twisted.internet.defer import inlineCallbacks, succeed |
854 | + |
855 | +from txaws.ec2.model import Instance, SecurityGroup |
856 | + |
857 | +from ensemble.errors import EnvironmentNotFound, ProviderInteractionError |
858 | from ensemble.providers.common.launch import ( |
859 | DEFAULT_REPOSITORIES, DEFAULT_PACKAGES) |
860 | from ensemble.providers.ec2.machine import EC2ProviderMachine |
861 | @@ -17,7 +17,7 @@ |
862 | |
863 | class EC2MachineLaunchTest(EC2TestMixin, EC2MachineLaunchMixin, TestCase): |
864 | |
865 | - def _mock_launch(self, instance=None, custom_verify=None): |
866 | + def _mock_launch(self, instance=None, custom_verify=None, machine_id=None): |
867 | |
868 | def verify_user_data(data): |
869 | lines = data.split("\n") |
870 | @@ -37,7 +37,9 @@ |
871 | instance_type="m1.small", |
872 | max_count=1, |
873 | min_count=1, |
874 | - security_groups=["%s-%s" % ("ensemble", self.env_name)], |
875 | + security_groups=[ |
876 | + "%s-%s" % ("ensemble", self.env_name), |
877 | + "%s-%s-%s" % ("ensemble", self.env_name, machine_id)], |
878 | user_data=MATCH(verify_user_data)) |
879 | |
880 | if instance: |
881 | @@ -45,6 +47,7 @@ |
882 | else: |
883 | self.mocker.result(succeed([])) |
884 | |
885 | + @inlineCallbacks |
886 | def test_provider_launch(self): |
887 | """ |
888 | The provider can be used to launch a machine with a minimal set of |
889 | @@ -55,27 +58,101 @@ |
890 | self.ec2.describe_security_groups() |
891 | self.mocker.result(succeed([])) |
892 | self._mock_create_group() |
893 | - self._mock_launch_utils(region="us-east-1") |
894 | - self._mock_get_zookeeper_hosts() |
895 | - self._mock_launch(instance) |
896 | - self.mocker.replay() |
897 | - |
898 | - def verify_result(result): |
899 | - self.assertEqual(len(result), 1) |
900 | - result = result.pop() |
901 | - self.assertTrue(isinstance(result, EC2ProviderMachine)) |
902 | - self.assertEqual(result.instance_id, instance.instance_id) |
903 | - |
904 | - provider = self.get_provider() |
905 | - d = provider.start_machine({"machine-id": "machine-1"}) |
906 | - d.addCallback(verify_result) |
907 | - return d |
908 | + self._mock_create_machine_group("machine-1") |
909 | + self._mock_launch_utils(region="us-east-1") |
910 | + self._mock_get_zookeeper_hosts() |
911 | + self._mock_launch(instance, machine_id="machine-1") |
912 | + self.mocker.replay() |
913 | + |
914 | + provider = self.get_provider() |
915 | + provided_machines = yield provider.start_machine( |
916 | + {"machine-id": "machine-1"}) |
917 | + self.assertEqual(len(provided_machines), 1) |
918 | + self.assertTrue(isinstance(provided_machines[0], EC2ProviderMachine)) |
919 | + self.assertEqual( |
920 | + provided_machines[0].instance_id, instance.instance_id) |
921 | + |
922 | + @inlineCallbacks |
923 | + def test_provider_launch_existing_security_group(self): |
924 | + """Verify that the launch works if the env security group exists""" |
925 | + instance = Instance("i-foobar", "running", dns_name="x1.example.com") |
926 | + security_group = SecurityGroup("ensemble-moon", "some description") |
927 | + |
928 | + self.ec2.describe_security_groups() |
929 | + self.mocker.result(succeed([security_group])) |
930 | + self._mock_create_machine_group("machine-1") |
931 | + self._mock_launch_utils(region="us-east-1") |
932 | + self._mock_get_zookeeper_hosts() |
933 | + self._mock_launch(instance, machine_id="machine-1") |
934 | + self.mocker.replay() |
935 | + |
936 | + provider = self.get_provider() |
937 | + provided_machines = yield provider.start_machine( |
938 | + {"machine-id": "machine-1"}) |
939 | + self.assertEqual(len(provided_machines), 1) |
940 | + self.assertTrue(isinstance(provided_machines[0], EC2ProviderMachine)) |
941 | + self.assertEqual( |
942 | + provided_machines[0].instance_id, instance.instance_id) |
943 | + |
944 | + @inlineCallbacks |
945 | + def test_provider_launch_existing_machine_security_group(self): |
946 | + """Verify that the launch works if the machine security group exists""" |
947 | + instance = Instance("i-foobar", "running", dns_name="x1.example.com") |
948 | + machine_group = SecurityGroup( |
949 | + "ensemble-moon-machine-1", "some description") |
950 | + |
951 | + self.ec2.describe_security_groups() |
952 | + self.mocker.result(succeed([machine_group])) |
953 | + self._mock_create_group() |
954 | + self._mock_delete_machine_group("machine-1") # delete existing sg |
955 | + self._mock_create_machine_group("machine-1") # then recreate |
956 | + self._mock_launch_utils(region="us-east-1") |
957 | + self._mock_get_zookeeper_hosts() |
958 | + self._mock_launch(instance, machine_id="machine-1") |
959 | + self.mocker.replay() |
960 | + |
961 | + provider = self.get_provider() |
962 | + provided_machines = yield provider.start_machine( |
963 | + {"machine-id": "machine-1"}) |
964 | + self.assertEqual(len(provided_machines), 1) |
965 | + self.assertTrue(isinstance(provided_machines[0], EC2ProviderMachine)) |
966 | + self.assertEqual( |
967 | + provided_machines[0].instance_id, instance.instance_id) |
968 | + |
969 | + @inlineCallbacks |
970 | + def test_provider_launch_existing_machine_security_group_is_active(self): |
971 | + """Verify launch fails properly if the machine group is stil active. |
972 | + |
973 | + This condition occurs when there is a corresponding machine in |
974 | + that security group, generally because it is still shutting |
975 | + down.""" |
976 | + machine_group = SecurityGroup( |
977 | + "ensemble-moon-machine-1", "some description") |
978 | + |
979 | + self.ec2.describe_security_groups() |
980 | + self.mocker.result(succeed([machine_group])) |
981 | + self._mock_create_group() |
982 | + self._mock_delete_machine_group_was_deleted("machine-1") # sg is gone! |
983 | + self._mock_get_zookeeper_hosts() |
984 | + self.mocker.replay() |
985 | + |
986 | + provider = self.get_provider() |
987 | + ex = yield self.assertFailure( |
988 | + provider.start_machine({"machine-id": "machine-1"}), |
989 | + ProviderInteractionError) |
990 | + self.assertEqual( |
991 | + str(ex), |
992 | + self.get_wrapped_ec2_error_text( |
993 | + "ensemble-moon-machine-1", "delete security group", |
994 | + "There are active instances using security group %r" |
995 | + )) |
996 | |
997 | def test_provider_type_machine_variable(self): |
998 | """The provider type is available via cloud-init.""" |
999 | self.ec2.describe_security_groups() |
1000 | self.mocker.result(succeed([])) |
1001 | self._mock_create_group() |
1002 | + self._mock_create_machine_group("machine-1") |
1003 | self._mock_launch_utils(region="us-east-1") |
1004 | self._mock_get_zookeeper_hosts() |
1005 | |
1006 | @@ -83,7 +160,8 @@ |
1007 | self.assertEqual( |
1008 | data["machine-data"]["ensemble-provider-type"], "ec2") |
1009 | |
1010 | - self._mock_launch(custom_verify=verify_provider_type) |
1011 | + self._mock_launch(custom_verify=verify_provider_type, |
1012 | + machine_id="machine-1") |
1013 | self.mocker.replay() |
1014 | |
1015 | provider = self.get_provider() |
1016 | @@ -128,6 +206,7 @@ |
1017 | self.ec2.describe_security_groups() |
1018 | self.mocker.result(succeed([])) |
1019 | self._mock_create_group() |
1020 | + self._mock_create_machine_group("machine-1") |
1021 | self._mock_launch_utils(region="us-east-1") |
1022 | self._mock_get_zookeeper_hosts() |
1023 | |
1024 | @@ -139,7 +218,8 @@ |
1025 | "--pidfile=/var/run/ensemble/machine-agent.pid") |
1026 | self.assertEqual(script, data["runcmd"][-1]) |
1027 | |
1028 | - self._mock_launch(custom_verify=verify_machine_agent) |
1029 | + self._mock_launch(custom_verify=verify_machine_agent, |
1030 | + machine_id="machine-1") |
1031 | self.mocker.replay() |
1032 | |
1033 | provider = self.get_provider() |
1034 | @@ -151,9 +231,10 @@ |
1035 | self.ec2.describe_security_groups() |
1036 | self.mocker.result(succeed([])) |
1037 | self._mock_create_group() |
1038 | + self._mock_create_machine_group("machine-1") |
1039 | self._mock_launch_utils(**get_ami_kwargs) |
1040 | self._mock_get_zookeeper_hosts() |
1041 | - self._mock_launch(instance) |
1042 | + self._mock_launch(instance, machine_id="machine-1") |
1043 | |
1044 | def test_launch_options_known_ami(self): |
1045 | self._mock_launch_with_ami_params({}) |
1046 | |
1047 | === added file 'ensemble/providers/ec2/tests/test_securitygroup.py' |
1048 | --- ensemble/providers/ec2/tests/test_securitygroup.py 1970-01-01 00:00:00 +0000 |
1049 | +++ ensemble/providers/ec2/tests/test_securitygroup.py 2011-08-10 18:22:55 +0000 |
1050 | @@ -0,0 +1,129 @@ |
1051 | +import logging |
1052 | + |
1053 | +from twisted.internet.defer import fail, succeed, inlineCallbacks |
1054 | +from txaws.ec2.model import IPPermission, SecurityGroup |
1055 | + |
1056 | +from ensemble.errors import ProviderInteractionError |
1057 | +from ensemble.lib.testing import TestCase |
1058 | +from ensemble.machine import ProviderMachine |
1059 | +from ensemble.providers.ec2.securitygroup import ( |
1060 | + open_provider_port, close_provider_port, get_provider_opened_ports) |
1061 | + |
1062 | +from .common import EC2TestMixin |
1063 | + |
1064 | + |
1065 | +class EC2SecurityGroupTest(EC2TestMixin, TestCase): |
1066 | + |
1067 | + @inlineCallbacks |
1068 | + def test_open_provider_port(self): |
1069 | + """Verify open port op will use the correct EC2 API.""" |
1070 | + log = self.capture_logging("ensemble.ec2", level=logging.DEBUG) |
1071 | + machine = ProviderMachine("i-foobar", "x1.example.com") |
1072 | + self.ec2.authorize_security_group( |
1073 | + "ensemble-moon-machine-1", ip_protocol="tcp", from_port="80", |
1074 | + to_port="80", cidr_ip="0.0.0.0/0") |
1075 | + self.mocker.result(succeed(True)) |
1076 | + self.mocker.replay() |
1077 | + |
1078 | + provider = self.get_provider() |
1079 | + yield open_provider_port(provider, machine, "machine-1", 80, "tcp") |
1080 | + self.assertIn( |
1081 | + "Opened 80/tcp on provider machine 'i-foobar'", |
1082 | + log.getvalue()) |
1083 | + |
1084 | + @inlineCallbacks |
1085 | + def test_close_provider_port(self): |
1086 | + """Verify close port op will use the correct EC2 API.""" |
1087 | + log = self.capture_logging("ensemble.ec2", level=logging.DEBUG) |
1088 | + machine = ProviderMachine("i-foobar", "x1.example.com") |
1089 | + self.ec2.revoke_security_group( |
1090 | + "ensemble-moon-machine-1", ip_protocol="tcp", from_port="80", |
1091 | + to_port="80", cidr_ip="0.0.0.0/0") |
1092 | + self.mocker.result(succeed(True)) |
1093 | + self.mocker.replay() |
1094 | + |
1095 | + provider = self.get_provider() |
1096 | + yield close_provider_port(provider, machine, "machine-1", 80, "tcp") |
1097 | + self.assertIn( |
1098 | + "Closed 80/tcp on provider machine 'i-foobar'", |
1099 | + log.getvalue()) |
1100 | + |
1101 | + @inlineCallbacks |
1102 | + def test_get_provider_opened_ports(self): |
1103 | + """Verify correct parse of IP perms from describe_security_group.""" |
1104 | + self.ec2.describe_security_groups("ensemble-moon-machine-1") |
1105 | + self.mocker.result(succeed([ |
1106 | + SecurityGroup( |
1107 | + "ensemble-%s-machine-1" % self.env_name, |
1108 | + "a security group name", |
1109 | + ips=[ |
1110 | + IPPermission("udp", "53", "53", "0.0.0.0/0"), |
1111 | + IPPermission("tcp", "80", "80", "0.0.0.0/0"), |
1112 | + # The range 8080-8082 will be ignored |
1113 | + IPPermission("tcp", "8080", "8082", "0.0.0.0/0"), |
1114 | + # Ignore permissions that are not 0.0.0.0/0 |
1115 | + IPPermission("tcp", "443", "443", "10.1.2.3") |
1116 | + ])])) |
1117 | + self.mocker.replay() |
1118 | + |
1119 | + provider = self.get_provider() |
1120 | + machine = ProviderMachine( |
1121 | + "i-foobar", "x1.example.com") |
1122 | + opened_ports = yield get_provider_opened_ports( |
1123 | + provider, machine, "machine-1") |
1124 | + self.assertEqual(opened_ports, set([(53, "udp"), (80, "tcp")])) |
1125 | + |
1126 | + @inlineCallbacks |
1127 | + def test_open_provider_port_unknown_instance(self): |
1128 | + """Verify open port op will use the correct EC2 API.""" |
1129 | + machine = ProviderMachine("i-foobar", "x1.example.com") |
1130 | + self.ec2.authorize_security_group( |
1131 | + "ensemble-moon-machine-1", ip_protocol="tcp", from_port="80", |
1132 | + to_port="80", cidr_ip="0.0.0.0/0") |
1133 | + self.mocker.result(fail(self.get_ec2_error("i-foobar"))) |
1134 | + self.mocker.replay() |
1135 | + |
1136 | + provider = self.get_provider() |
1137 | + ex = yield self.assertFailure( |
1138 | + open_provider_port(provider, machine, "machine-1", 80, "tcp"), |
1139 | + ProviderInteractionError) |
1140 | + self.assertEqual( |
1141 | + str(ex), |
1142 | + self.get_wrapped_ec2_error_text( |
1143 | + "i-foobar", "open 80/tcp on machine")) |
1144 | + |
1145 | + @inlineCallbacks |
1146 | + def test_close_provider_port_unknown_instance(self): |
1147 | + """Verify open port op will use the correct EC2 API.""" |
1148 | + machine = ProviderMachine("i-foobar", "x1.example.com") |
1149 | + self.ec2.revoke_security_group( |
1150 | + "ensemble-moon-machine-1", ip_protocol="tcp", from_port="80", |
1151 | + to_port="80", cidr_ip="0.0.0.0/0") |
1152 | + self.mocker.result(fail(self.get_ec2_error("i-foobar"))) |
1153 | + self.mocker.replay() |
1154 | + |
1155 | + provider = self.get_provider() |
1156 | + ex = yield self.assertFailure( |
1157 | + close_provider_port(provider, machine, "machine-1", 80, "tcp"), |
1158 | + ProviderInteractionError) |
1159 | + self.assertEqual( |
1160 | + str(ex), |
1161 | + self.get_wrapped_ec2_error_text( |
1162 | + "i-foobar", "close 80/tcp on machine")) |
1163 | + |
1164 | + @inlineCallbacks |
1165 | + def test_get_provider_opened_ports_unknown_instance(self): |
1166 | + """Verify open port op will use the correct EC2 API.""" |
1167 | + self.ec2.describe_security_groups("ensemble-moon-machine-1") |
1168 | + self.mocker.result(fail(self.get_ec2_error("i-foobar"))) |
1169 | + self.mocker.replay() |
1170 | + |
1171 | + provider = self.get_provider() |
1172 | + machine = ProviderMachine("i-foobar", "x1.example.com") |
1173 | + ex = yield self.assertFailure( |
1174 | + get_provider_opened_ports(provider, machine, "machine-1"), |
1175 | + ProviderInteractionError) |
1176 | + self.assertEqual( |
1177 | + str(ex), |
1178 | + self.get_wrapped_ec2_error_text( |
1179 | + "i-foobar", "get opened ports on machine")) |
1180 | |
1181 | === modified file 'ensemble/providers/orchestra/launch.py' |
1182 | --- ensemble/providers/orchestra/launch.py 2011-08-09 21:19:13 +0000 |
1183 | +++ ensemble/providers/orchestra/launch.py 2011-08-10 18:22:55 +0000 |
1184 | @@ -62,7 +62,7 @@ |
1185 | class OrchestraLaunchMachine(LaunchMachine): |
1186 | |
1187 | @inlineCallbacks |
1188 | - def start_machine(self, variables): |
1189 | + def start_machine(self, variables, machine_id): |
1190 | cobbler = self._provider.cobbler |
1191 | instance_id = yield cobbler.acquire_system() |
1192 | ks_meta = self._build_ks_meta(instance_id, variables) |
1193 | |
1194 | === modified file 'ensemble/providers/orchestra/tests/common.py' |
1195 | --- ensemble/providers/orchestra/tests/common.py 2011-08-09 21:19:13 +0000 |
1196 | +++ ensemble/providers/orchestra/tests/common.py 2011-08-10 18:22:55 +0000 |
1197 | @@ -153,4 +153,3 @@ |
1198 | self.mocker.result(succeed([name])) |
1199 | self.proxy_m.callRemote("get_system", name) |
1200 | self.mocker.result(succeed({"name": name, "uid": uid})) |
1201 | - |
1202 | |
1203 | === modified file 'ensemble/providers/tests/test_dummy.py' |
1204 | --- ensemble/providers/tests/test_dummy.py 2011-07-31 00:20:08 +0000 |
1205 | +++ ensemble/providers/tests/test_dummy.py 2011-08-10 18:22:55 +0000 |
1206 | @@ -57,7 +57,8 @@ |
1207 | |
1208 | @inlineCallbacks |
1209 | def test_start_machine_accepts_machine_data(self): |
1210 | - machines = yield self.provider.start_machine({"machine-id": 0, "a": 1}) |
1211 | + machines = yield self.provider.start_machine( |
1212 | + {"machine-id": 0, "a": 1}) |
1213 | self.assertEqual(len(machines), 1) |
1214 | self.assertTrue(isinstance(machines[0], ProviderMachine)) |
1215 | |
1216 | @@ -141,13 +142,13 @@ |
1217 | """Verifies dummy provider properly works with ports.""" |
1218 | machines = yield self.provider.start_machine({"machine-id": 0}) |
1219 | machine = machines[0] |
1220 | - yield self.provider.open_port(machine, 25, "tcp") |
1221 | - yield self.provider.open_port(machine, 80) |
1222 | - yield self.provider.open_port(machine, 53, "udp") |
1223 | - yield self.provider.open_port(machine, 443, "tcp") |
1224 | - yield self.provider.close_port(machine, 25) |
1225 | - yield self.provider.close_port(machine, 25) # ignored |
1226 | - exposed_ports = yield self.provider.get_opened_ports(machine) |
1227 | + yield self.provider.open_port(machine, 0, 25, "tcp") |
1228 | + yield self.provider.open_port(machine, 0, 80) |
1229 | + yield self.provider.open_port(machine, 0, 53, "udp") |
1230 | + yield self.provider.open_port(machine, 0, 443, "tcp") |
1231 | + yield self.provider.close_port(machine, 0, 25) |
1232 | + yield self.provider.close_port(machine, 0, 25) # ignored |
1233 | + exposed_ports = yield self.provider.get_opened_ports(machine, 0) |
1234 | self.assertEqual(exposed_ports, |
1235 | set([(53, 'udp'), (80, 'tcp'), (443, 'tcp')])) |
1236 | |
1237 | |
1238 | === modified file 'examples/wordpress/hooks/db-relation-changed' |
1239 | --- examples/wordpress/hooks/db-relation-changed 2011-05-03 08:54:13 +0000 |
1240 | +++ examples/wordpress/hooks/db-relation-changed 2011-08-10 18:22:55 +0000 |
1241 | @@ -98,3 +98,6 @@ |
1242 | # Restart apache |
1243 | ensemble-log "Restarting apache2 service" |
1244 | /etc/init.d/apache2 restart |
1245 | + |
1246 | +# Make it publicly visible, once the wordpress service is exposed |
1247 | +open-port 80/tcp |
* Tiny, irrelevant note: rather than directly creating an EC2ProviderMachine, there's now a machine_ from_instance function in ec2.machine which is a touch more convenient.
* Significant issue: I don't like having both 'machine' and 'machine_id' parameters: if there isn't currently a way to determine machine_id given a machine, I think it would be worth adding one and using it, to avoid cluttering up the Provider interface.