Merge lp:~jimbaker/pyjuju/expose-provider-ec2 into lp:pyjuju

Proposed by Jim Baker
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
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Approve
William Reade (community) Approve
Review via email: mp+68478@code.launchpad.net

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-ENVIRONMENT", along with a per-machine
   security group named "ensemble-ENVIRONMENT-MACHINE_ID", with no
   ports open.

 * The EC2 provider implementation of open_port, close_port, and
   get_opened_ports corresponds to EC2 operations of
   authorize_security_group, revoke_security_group, and
   describe_security_groups (using txaws). For the latter, the
   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.agents.provision DEBUG: Service 'wordpress' is exposed
  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

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

* 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.

review: Needs Fixing
Revision history for this message
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.

review: Needs Fixing
Revision history for this message
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_from_instance function in
> 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.

Revision history for this message
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.

Revision history for this message
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 :))

review: Approve
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :
Download full text (4.2 KiB)

Here are a few additional points Jim. Nothing huge, I think:

[1]

         self._watched_services = {}
+ self._watched_machines = set()

Bogus line. It's duplicated.

[2]

             machine = yield self.provider.get_machine(instance_id)
      machine.machine_id = machine_id
      (...)
             current_ports = yield self.provider.get_opened_ports(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_dns_name=None, launch_time=None, machine_id=None):

Per above, please drop this.

[4]

- launch_deferred = self.get_machine_variables(machine_data)
- launch_deferred.addCallback(self.start_machine)
+ machine_variables = yield self.get_machine_variables(machine_data)
+ 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_port(self, machine, port, protocol)

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.exception.EC2Error: Error Message:
+ # The instance ID 'i-afe113ce' does not exist
+ raise ProviderInteractionError(
+ "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_machine_group in group_ids:
+ yield self._provider.ec2.delete_security_group(
+ ensemble_machine_group)

Has this logic ...

Read more...

review: Needs Fixing
Revision history for this message
Jim Baker (jimbaker) wrote :
Download full text (5.7 KiB)

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_services = {}
> + self._watched_machines = set()
>
>
> Bogus line. It's duplicated.
>

Fixed

>
>
> [2]
>
> machine = yield self.provider.get_machine(instance_id)
> machine.machine_id = machine_id
> (...)
> current_ports = yield self.provider.get_opened_ports(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.
>

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_dns_name=None, launch_time=None,
> machine_id=None):
>
> Per above, please drop this.
>

Reverted

>
> [4]
>
> - launch_deferred = self.get_machine_variables(machine_data)
> - launch_deferred.addCallback(self.start_machine)
> + machine_variables = yield self.get_machine_variables(machine_data)
> + 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_port(self, machine, port, protocol)
>
> 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.exception.EC2Error: Error Message:
> + # The instance ID 'i-afe113ce' does not exist
> + raise ProviderInteractionError(
> + "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...

Read more...

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Looks good, +1 with a few extra comments:

[13]

+ raise ProviderInteractionError(
+ "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 ProviderInteractionError(
+ "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_machine_group)
+ 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.instance_id, ex, port, protocol))

"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_groups[0].allowed_ips:
+ if ip_permission.cidr_ip != "0.0.0.0/0":

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.

review: Approve
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

Revision history for this message
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 ProviderInteractionError(
> + "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 ProviderInteractionError(
> + "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_machine_group)
> + 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 ProviderInteractionError. This is because this is
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.instance_id, ex, port, protocol))
>
> "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_groups[0].allowed_ips:
> + if ip_permission.cidr_ip != "0.0.0.0/0":
>
> 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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ensemble/agents/provision.py'
--- ensemble/agents/provision.py 2011-08-10 18:22:54 +0000
+++ ensemble/agents/provision.py 2011-08-10 18:22:55 +0000
@@ -317,8 +317,10 @@
317 yield self.open_close_ports(unit_state)317 yield self.open_close_ports(unit_state)
318318
319 if not exposed:319 if not exposed:
320 log.debug("Service %r is unexposed", service_name)
320 self._watched_services[service_name] = NotExposed321 self._watched_services[service_name] = NotExposed
321 else:322 else:
323 log.debug("Service %r is exposed", service_name)
322 self._watched_services[service_name] = set()324 self._watched_services[service_name] = set()
323 yield self._setup_service_unit_watch(service_state)325 yield self._setup_service_unit_watch(service_state)
324326
@@ -332,7 +334,7 @@
332 @inlineCallbacks334 @inlineCallbacks
333 def cb_check_service_units(old_service_units, new_service_units):335 def cb_check_service_units(old_service_units, new_service_units):
334 watched_units = self._watched_services.get(336 watched_units = self._watched_services.get(
335 service_state.service_name)337 service_state.service_name, NotExposed)
336 if not self._running or watched_units is NotExposed:338 if not self._running or watched_units is NotExposed:
337 raise StopWatcher()339 raise StopWatcher()
338340
@@ -447,14 +449,15 @@
447 for port in ports:449 for port in ports:
448 policy_ports.add(450 policy_ports.add(
449 (port["port"], port["proto"]))451 (port["port"], port["proto"]))
450452 current_ports = yield self.provider.get_opened_ports(
451 current_ports = yield self.provider.get_opened_ports(machine)453 machine, machine_id)
452 to_open = policy_ports - current_ports454 to_open = policy_ports - current_ports
453 to_close = current_ports - policy_ports455 to_close = current_ports - policy_ports
454 for port, proto in to_open:456 for port, proto in to_open:
455 yield self.provider.open_port(machine, port, proto)457 yield self.provider.open_port(machine, machine_id, port, proto)
456 for port, proto in to_close:458 for port, proto in to_close:
457 yield self.provider.close_port(machine, port, proto)459 yield self.provider.close_port(
460 machine, machine_id, port, proto)
458 except ProviderInteractionError:461 except ProviderInteractionError:
459 log.info("No provisioned machine for machine %r", machine_id)462 log.info("No provisioned machine for machine %r", machine_id)
460 finally:463 finally:
461464
=== modified file 'ensemble/agents/tests/test_provision.py'
--- ensemble/agents/tests/test_provision.py 2011-08-10 18:22:54 +0000
+++ ensemble/agents/tests/test_provision.py 2011-08-10 18:22:55 +0000
@@ -652,7 +652,7 @@
652 instance_id = yield machine.get_instance_id()652 instance_id = yield machine.get_instance_id()
653 machine_provider = yield self.agent.provider.get_machine(instance_id)653 machine_provider = yield self.agent.provider.get_machine(instance_id)
654 provider_ports = yield self.agent.provider.get_opened_ports(654 provider_ports = yield self.agent.provider.get_opened_ports(
655 machine_provider)655 machine_provider, machine.id)
656 returnValue(provider_ports)656 returnValue(provider_ports)
657657
658 def test_open_close_ports_on_machine(self):658 def test_open_close_ports_on_machine(self):
659659
=== modified file 'ensemble/agents/tests/test_unit.py'
--- ensemble/agents/tests/test_unit.py 2011-07-13 09:04:36 +0000
+++ ensemble/agents/tests/test_unit.py 2011-08-10 18:22:55 +0000
@@ -265,8 +265,7 @@
265 "app-relation-changed", executor=self.agent.executor)265 "app-relation-changed", executor=self.agent.executor)
266266
267 # trigger the hook that will read service options267 # trigger the hook that will read service options
268 wordpress_states = yield self.add_opposite_service_unit(268 yield self.add_opposite_service_unit(self.states)
269 self.states)
270269
271 # Verify the hook has executed.270 # Verify the hook has executed.
272 yield hook_complete271 yield hook_complete
273272
=== modified file 'ensemble/agents/unit.py'
--- ensemble/agents/unit.py 2011-07-21 01:15:02 +0000
+++ ensemble/agents/unit.py 2011-08-10 18:22:55 +0000
@@ -188,7 +188,8 @@
188 log.debug("Configuration Changed")188 log.debug("Configuration Changed")
189189
190 if current_state != "started":190 if current_state != "started":
191 log.debug("Configuration updated on service in a non-started state")191 log.debug(
192 "Configuration updated on service in a non-started state")
192 returnValue(None)193 returnValue(None)
193194
194 yield self.workflow.fire_transition("reconfigure")195 yield self.workflow.fire_transition("reconfigure")
195196
=== modified file 'ensemble/machine/__init__.py'
--- ensemble/machine/__init__.py 2011-07-13 22:08:49 +0000
+++ ensemble/machine/__init__.py 2011-08-10 18:22:55 +0000
@@ -2,14 +2,15 @@
22
3class ProviderMachine(object):3class ProviderMachine(object):
4 """4 """
5 Representative of a machine resource created by a C{MachineProvider}. The5 Representative of a machine resource created by a
6 object is typically annotated by the machine provider, such that the6 :class:`MachineProvider`. The object is typically annotated by the
7 provider can perform subsequent actions upon it, using the additional7 machine provider, such that the provider can perform subsequent
8 metadata for identification, without leaking these details to consumers of8 actions upon it, using the additional metadata for identification,
9 the C{MachineProvider} api.9 without leaking these details to consumers of the
10 :class:`MachineProvider` api.
10 """11 """
1112
12 def __init__(self, instance_id, dns_name=None, 13 def __init__(self, instance_id, dns_name=None,
13 private_dns_name=None, launch_time=None):14 private_dns_name=None, launch_time=None):
14 self.instance_id = instance_id15 self.instance_id = instance_id
15 # ideally this would be ip_address, but txaws doesn't expose it.16 # ideally this would be ip_address, but txaws doesn't expose it.
1617
=== modified file 'ensemble/machine/tests/test_machine.py'
--- ensemble/machine/tests/test_machine.py 2011-01-26 23:39:35 +0000
+++ ensemble/machine/tests/test_machine.py 2011-08-10 18:22:55 +0000
@@ -1,6 +1,4 @@
1
2from ensemble.lib.testing import TestCase1from ensemble.lib.testing import TestCase
3
4from ensemble.machine import ProviderMachine2from ensemble.machine import ProviderMachine
53
64
75
=== modified file 'ensemble/providers/common/launch.py'
--- ensemble/providers/common/launch.py 2011-08-03 14:48:50 +0000
+++ ensemble/providers/common/launch.py 2011-08-10 18:22:55 +0000
@@ -1,6 +1,6 @@
1import copy1import copy
22
3from twisted.internet.defer import inlineCallbacks, returnValue, fail3from twisted.internet.defer import inlineCallbacks, returnValue
44
5from ensemble.errors import ProviderError5from ensemble.errors import ProviderError
6from ensemble.state.auth import make_identity6from ensemble.state.auth import make_identity
@@ -69,31 +69,37 @@
69 self._provider = provider69 self._provider = provider
70 self._bootstrap = bootstrap70 self._bootstrap = bootstrap
7171
72 @inlineCallbacks
72 def run(self, machine_data):73 def run(self, machine_data):
73 """Launch an instance node within the machine provider environment.74 """Launch an instance node within the machine provider environment.
7475
75 @param machine_data a dictionary of data that is passed along to76 `machine_data`: a dictionary of data that is passed along to
76 provided to the machine as serialized yaml. 'machine-id' is a77 provided to the machine as serialized yaml. 'machine-id' is a
77 required key, denoting the machine's zookeeper node for its78 required key, denoting the machine's zookeeper node for its
78 machine agent.79 machine agent.
79 """80 """
80 if "machine-id" not in machine_data:81 if "machine-id" not in machine_data:
81 return fail(ProviderError(82 raise ProviderError(
82 "Machine state `machine-id` not provided in machine_data."))83 "Machine state `machine-id` not provided in machine_data.")
8384
84 launch_deferred = self.get_machine_variables(machine_data)85 machine_variables = yield self.get_machine_variables(machine_data)
85 launch_deferred.addCallback(self.start_machine)86 provider_machines = yield self.start_machine(
87 machine_variables, machine_data["machine-id"])
86 if self._bootstrap:88 if self._bootstrap:
87 launch_deferred.addCallback(self._on_bootstrap_launched)89 yield self._on_bootstrap_launched(provider_machines)
88 return launch_deferred90 returnValue(provider_machines)
8991
90 def start_machine(self, variables):92 def start_machine(self, machine_variables, machine_id):
91 """Actually launch a machine for the appropriate provider.93 """Actually launch a machine for the appropriate provider.
9294
93 @param variables: non-provider-specific data, sufficient to95 `machine_variables`: non-provider-specific data, sufficient to
94 define the machine's behaviour once it exists.96 define the machine's behaviour once it exists.
9597
96 @return: a list of newly-launched ProviderMachines.98 `machine_id`: the external machine ID (also exists in the
99 above bag of data)
100
101 Returns a singe-entry list containing a ProviderMachine for the
102 new instance
97 """103 """
98 raise NotImplementedError()104 raise NotImplementedError()
99105
@@ -169,7 +175,8 @@
169 return [175 return [
170 "sudo apt-get install -y python-txzookeeper",176 "sudo apt-get install -y python-txzookeeper",
171 "sudo mkdir -p /usr/lib/ensemble",177 "sudo mkdir -p /usr/lib/ensemble",
172 "cd /usr/lib/ensemble && sudo /usr/bin/bzr co %s ensemble" % branch,178 "cd /usr/lib/ensemble && sudo /usr/bin/bzr co %s ensemble" % \
179 branch,
173 "cd /usr/lib/ensemble/ensemble && sudo python setup.py develop"]180 "cd /usr/lib/ensemble/ensemble && sudo python setup.py develop"]
174181
175 def _get_initialize_script(self):182 def _get_initialize_script(self):
176183
=== modified file 'ensemble/providers/common/tests/test_launch.py'
--- ensemble/providers/common/tests/test_launch.py 2011-08-04 09:50:57 +0000
+++ ensemble/providers/common/tests/test_launch.py 2011-08-10 18:22:55 +0000
@@ -1,10 +1,9 @@
1from cStringIO import StringIO
2import logging1import logging
3import tempfile2import tempfile
43
5from twisted.internet.defer import fail, succeed4from twisted.internet.defer import fail, succeed
65
7from ensemble.errors import EnvironmentNotFound, FileNotFound, ProviderError6from ensemble.errors import EnvironmentNotFound, ProviderError
8from ensemble.lib.testing import TestCase7from ensemble.lib.testing import TestCase
9from ensemble.providers.common.bootstrap import Bootstrap8from ensemble.providers.common.bootstrap import Bootstrap
10from ensemble.providers.common.launch import (9from ensemble.providers.common.launch import (
@@ -18,7 +17,7 @@
1817
19class DummyLaunchMachine(LaunchMachine):18class DummyLaunchMachine(LaunchMachine):
2019
21 def start_machine(self, variables):20 def start_machine(self, variables, data):
22 if self._bootstrap:21 if self._bootstrap:
23 name = "bootstrapped-instance-id"22 name = "bootstrapped-instance-id"
24 else:23 else:
2524
=== modified file 'ensemble/providers/dummy.py'
--- ensemble/providers/dummy.py 2011-08-10 18:22:54 +0000
+++ ensemble/providers/dummy.py 2011-08-10 18:22:55 +0000
@@ -122,7 +122,7 @@
122 config["dynamicduck"] = "magic"122 config["dynamicduck"] = "magic"
123 return config123 return config
124124
125 def open_port(self, machine, port, protocol="tcp"):125 def open_port(self, machine, machine_id, port, protocol="tcp"):
126 """Dummy equivalent of ec2-authorize-group"""126 """Dummy equivalent of ec2-authorize-group"""
127 if not isinstance(machine, DummyMachine):127 if not isinstance(machine, DummyMachine):
128 return fail(ProviderError("Invalid machine for provider"))128 return fail(ProviderError("Invalid machine for provider"))
@@ -131,7 +131,7 @@
131 port, protocol, machine.instance_id)131 port, protocol, machine.instance_id)
132 return succeed(None)132 return succeed(None)
133133
134 def close_port(self, machine, port, protocol="tcp"):134 def close_port(self, machine, machin_id, port, protocol="tcp"):
135 """Dummy equivalent of ec2-revoke-group"""135 """Dummy equivalent of ec2-revoke-group"""
136 if not isinstance(machine, DummyMachine):136 if not isinstance(machine, DummyMachine):
137 return fail(ProviderError("Invalid machine for provider"))137 return fail(ProviderError("Invalid machine for provider"))
@@ -143,7 +143,7 @@
143 pass143 pass
144 return succeed(None)144 return succeed(None)
145145
146 def get_opened_ports(self, machine):146 def get_opened_ports(self, machine, machine_id):
147 """Dummy equivalent of ec2-describe-group147 """Dummy equivalent of ec2-describe-group
148148
149 This returns the current exposed ports in the environment for149 This returns the current exposed ports in the environment for
150150
=== modified file 'ensemble/providers/ec2/__init__.py'
--- ensemble/providers/ec2/__init__.py 2011-08-05 12:43:39 +0000
+++ ensemble/providers/ec2/__init__.py 2011-08-10 18:22:55 +0000
@@ -16,6 +16,8 @@
16from .iterate import EC2MachineIteration16from .iterate import EC2MachineIteration
17from .launch import EC2LaunchMachine17from .launch import EC2LaunchMachine
18from .machine import get_machine18from .machine import get_machine
19from .securitygroup import (
20 open_provider_port, close_provider_port, get_provider_opened_ports)
19from .shutdown import EC2Shutdown, EC2ShutdownMachine21from .shutdown import EC2Shutdown, EC2ShutdownMachine
20from .utils import get_region_uri22from .utils import get_region_uri
2123
@@ -147,3 +149,15 @@
147 @raise: EnvironmentNotFound or EnvironmentPending149 @raise: EnvironmentNotFound or EnvironmentPending
148 """150 """
149 return find_zookeepers(self, get_machine)151 return find_zookeepers(self, get_machine)
152
153 def open_port(self, machine, machine_id, port, protocol="tcp"):
154 """Authorizes `port` using `protocol` on EC2 for `machine`."""
155 return open_provider_port(self, machine, machine_id, port, protocol)
156
157 def close_port(self, machine, machine_id, port, protocol="tcp"):
158 """Revokes `port` using `protocol` on EC2 for `machine`."""
159 return close_provider_port(self, machine, machine_id, port, protocol)
160
161 def get_opened_ports(self, machine, machine_id):
162 """Returns a set of open (port, proto) pairs for `machine`."""
163 return get_provider_opened_ports(self, machine, machine_id)
150164
=== modified file 'ensemble/providers/ec2/accessor.py'
--- ensemble/providers/ec2/accessor.py 2011-07-13 22:08:49 +0000
+++ ensemble/providers/ec2/accessor.py 2011-08-10 18:22:55 +0000
@@ -1,4 +1,5 @@
1from twisted.internet.defer import fail1from twisted.internet.defer import inlineCallbacks, returnValue
2from txaws.ec2.exception import EC2Error
23
3from ensemble.errors import ProviderInteractionError4from ensemble.errors import ProviderInteractionError
45
@@ -9,17 +10,21 @@
9class EC2MachineAccessor(EC2ProviderMachineFilter):10class EC2MachineAccessor(EC2ProviderMachineFilter):
10 """Retrieve a specific machine by provider id."""11 """Retrieve a specific machine by provider id."""
1112
13 @inlineCallbacks
12 def run(self, provider_id):14 def run(self, provider_id):
13 d = self._provider.ec2.describe_instances(provider_id)15 try:
14 d.addCallback(self._filter_provider_machines)16 instances = yield self._provider.ec2.describe_instances(
15 d.addCallback(self._create_provider_machine, provider_id)17 provider_id)
16 return d18 except EC2Error, e:
1719 # AWS may return an error when the instance is not found
18 def _create_provider_machine(self, instances, provider_id):20 # (but presumably eventually will be), eg,
19 if not instances:21 # txaws.ec2.exception.EC2Error: Error Message:
20 return fail(22 # The instance ID 'i-afe113ce' does not exist
21 ProviderInteractionError(23 raise ProviderInteractionError(
22 "Machine (id: %r) was not found" % provider_id))24 "EC2 error when looking up instance %s: %s" % (provider_id, e))
2325 filtered = self._filter_provider_machines(instances)
24 instance = instances.pop()26 if not filtered:
25 return machine_from_instance(instance)27 raise ProviderInteractionError(
28 "EC2 instance %s was not found" % provider_id)
29 instance = filtered.pop()
30 returnValue(machine_from_instance(instance))
2631
=== modified file 'ensemble/providers/ec2/launch.py'
--- ensemble/providers/ec2/launch.py 2011-07-18 07:07:03 +0000
+++ ensemble/providers/ec2/launch.py 2011-08-10 18:22:55 +0000
@@ -1,5 +1,7 @@
1from twisted.internet.defer import inlineCallbacks, returnValue1from twisted.internet.defer import inlineCallbacks, returnValue
2from txaws.ec2.exception import EC2Error
23
4from ensemble.errors import ProviderInteractionError
3from ensemble.providers.common.launch import LaunchMachine5from ensemble.providers.common.launch import LaunchMachine
46
5from .machine import machine_from_instance7from .machine import machine_from_instance
@@ -10,26 +12,30 @@
10 """Amazon EC2 operation for launching an instance"""12 """Amazon EC2 operation for launching an instance"""
1113
12 @inlineCallbacks14 @inlineCallbacks
13 def start_machine(self, variables):15 def start_machine(self, machine_variables, machine_id):
14 """Actually launch an instance on EC2.16 """Actually launch an instance on EC2.
1517
16 @param variables: should be a dictionary with entries sufficient18 `machine_variables`: non-provider-specific data, sufficient to
17 to specify everything necessary to launch the requested19 define the machine's behaviour once it exists.
18 instance.20
1921 `machine_id`: the external machine ID (also exists in the
20 @return: a singe-entry list containing a ProviderMachine for the22 above bag of data)
21 new instance23
24 Returns a singe-entry list containing a ProviderMachine for the
25 new instance
22 """26 """
23 # Retrieves standard ec2 run_instances arguments, of note27 # Retrieves standard ec2 run_instances arguments, of note
24 # image id, and instance type.28 # image id, and instance type.
25 machine_options = yield get_launch_options(29 machine_options = yield get_launch_options(
26 self._provider.config,30 self._provider.config,
27 **variables)31 **machine_variables)
2832
29 # Ensure the ensemble security group exists and is included in the33 # Ensure the ensemble security groups exist (both the overall
30 # machine instance launch options.34 # group and the machine-specific group) and are included in
35 # the machine instance launch options.
31 machine_options["security_groups"] = yield self._ensure_group(36 machine_options["security_groups"] = yield self._ensure_group(
32 machine_options["security_groups"])37 machine_options["security_groups"],
38 machine_id)
3339
34 # Launch the instance.40 # Launch the instance.
35 instances = yield self._provider.ec2.run_instances(**machine_options)41 instances = yield self._provider.ec2.run_instances(**machine_options)
@@ -54,24 +60,26 @@
54 vars_deferred.addCallback(set_provider_type)60 vars_deferred.addCallback(set_provider_type)
55 return vars_deferred61 return vars_deferred
5662
57 def _ensure_group(self, groups):63 @inlineCallbacks
64 def _ensure_group(self, groups, machine_id):
58 """Ensure the ensemble group is the machine launch groups.65 """Ensure the ensemble group is the machine launch groups.
5966
60 Machines launched by ensemble are tagged with a group so they can67 Machines launched by ensemble are tagged with a group so they
61 be distinguished from other machines that might be running on68 can be distinguished from other machines that might be running
62 an EC2 account. This group can be specified explicitly or implicitly69 on an EC2 account. This group can be specified explicitly or
63 defined by the environment name.70 implicitly defined by the environment name. In addition, a
6471 specific machine security group is created for each machine,
65 @param security_groups: The configured security groups for a machine72 so that its firewall rules can be configured per machine.
66 instance.73
74 `groups`: The configured security groups for a machine instance.
75
76 `machine_id`: The external machine ID of this machine instance
67 """77 """
68 # Label the instance with the ensemble name.78 # Label the instance with the ensemble name.
69 d = self._provider.ec2.describe_security_groups()79 security_groups = yield self._provider.ec2.describe_security_groups()
70 d.addCallback(self._on_received_groups, groups)
71 return d
72
73 def _on_received_groups(self, security_groups, groups):
74 ensemble_group = "ensemble-%s" % self._provider.environment_name80 ensemble_group = "ensemble-%s" % self._provider.environment_name
81 ensemble_machine_group = "ensemble-%s-%s" % (
82 self._provider.environment_name, machine_id)
75 group_ids = [group.name for group in security_groups]83 group_ids = [group.name for group in security_groups]
7684
77 # Ensure the provider group is added to the instance groups.85 # Ensure the provider group is added to the instance groups.
@@ -81,65 +89,49 @@
81 # Create the provider group if doesn't exist.89 # Create the provider group if doesn't exist.
82 if not ensemble_group in group_ids:90 if not ensemble_group in group_ids:
83 log.debug("Creating ensemble provider group %s", ensemble_group)91 log.debug("Creating ensemble provider group %s", ensemble_group)
84 group_created = self._provider.ec2.create_security_group(92 yield self._provider.ec2.create_security_group(
85 ensemble_group,93 ensemble_group,
86 "Ensemble group for %s" % self._provider.environment_name)94 "Ensemble group for %s" % self._provider.environment_name)
8795
88 # Authorize SSH.96 # Authorize SSH.
89 group_created.addCallback(97 yield self._provider.ec2.authorize_security_group(
90 lambda x: self._provider.ec2.authorize_security_group(98 ensemble_group,
91 ensemble_group,99 ip_protocol="tcp",
92 ip_protocol="tcp",100 from_port="22", to_port="22",
93 from_port="22", to_port="22",101 cidr_ip="0.0.0.0/0")
94 cidr_ip="0.0.0.0/0"))
95102
96 # We need to describe the group to pickup the owner_id for auth.103 # We need to describe the group to pickup the owner_id for auth.
97 group_created.addCallback(104 groups_info = yield self._provider.ec2.describe_security_groups(
98 lambda x: self._provider.ec2.describe_security_groups(105 ensemble_group)
99 ensemble_group))
100106
101 # Authorize Internal ZK Traffic107 # Authorize Internal ZK Traffic
102 group_created.addCallback(108 yield self._provider.ec2.authorize_security_group(
103 lambda groups: self._provider.ec2.authorize_security_group(109 ensemble_group,
104 ensemble_group,110 source_group_name=ensemble_group,
105 source_group_name=ensemble_group,111 source_group_owner_id=groups_info.pop().owner_id)
106 source_group_owner_id=groups.pop().owner_id))112
107113 # Create the machine-specific group, but first see if there's
108 # TODO: For now authorize external traffic to the instance,114 # one already existing from a previous machine launch;
109 # except for the ZooKeeper port. The expose functionality115 # if so, delete it, since it can have the wrong firewall setup
110 # that is in progress will address this in a better way.116 if ensemble_machine_group in group_ids:
111 group_created.addCallback(117 try:
112 lambda groups: self._provider.ec2.authorize_security_group(118 yield self._provider.ec2.delete_security_group(
113 ensemble_group,119 ensemble_machine_group)
114 ip_protocol="tcp",120 log.debug("Deleted existing machine group %s, will replace",
115 from_port="1",121 ensemble_machine_group)
116 to_port="2180",122 except EC2Error, e:
117 cidr_ip="0.0.0.0/0"))123 log.debug("Cannot delete security group %s: %s",
118 group_created.addCallback(124 ensemble_machine_group, e)
119 lambda groups: self._provider.ec2.authorize_security_group(125 raise ProviderInteractionError(
120 ensemble_group,126 "EC2 error when attempting to delete "
121 ip_protocol="tcp",127 "security group %s: %s" % (
122 from_port="2182",128 ensemble_machine_group, e))
123 to_port="65535",129 log.debug("Creating ensemble machine security group %s",
124 cidr_ip="0.0.0.0/0"))130 ensemble_machine_group)
125131 yield self._provider.ec2.create_security_group(
126 group_created.addCallback(132 ensemble_machine_group,
127 lambda groups: self._provider.ec2.authorize_security_group(133 "Ensemble group for %s machine %s" % (
128 ensemble_group,134 self._provider.environment_name, machine_id))
129 ip_protocol="udp",135 groups.append(ensemble_machine_group)
130 from_port="1",136
131 to_port="2180",137 returnValue(groups)
132 cidr_ip="0.0.0.0/0"))
133 group_created.addCallback(
134 lambda groups: self._provider.ec2.authorize_security_group(
135 ensemble_group,
136 ip_protocol="udp",
137 from_port="2182",
138 to_port="65535",
139 cidr_ip="0.0.0.0/0"))
140
141 # We use a separate callback here, because we need to wait
142 # on the authorize security group deferred to succeed before
143 # we can return the groups result.
144 return group_created.addCallback(lambda x: groups)
145 return groups
146138
=== added file 'ensemble/providers/ec2/securitygroup.py'
--- ensemble/providers/ec2/securitygroup.py 1970-01-01 00:00:00 +0000
+++ ensemble/providers/ec2/securitygroup.py 2011-08-10 18:22:55 +0000
@@ -0,0 +1,90 @@
1from twisted.internet.defer import inlineCallbacks, returnValue
2from txaws.ec2.exception import EC2Error
3
4from ensemble.errors import ProviderInteractionError
5
6from .utils import log
7
8
9def _get_machine_group_name(provider, machine_id):
10 """Get EC2 security group name associated just with `machine_id`."""
11 return "ensemble-%s-%s" % (provider.environment_name, machine_id)
12
13
14# TODO These security group functions do not handle the eventual
15# consistency seen with EC2. A future branch will add support for
16# retry so that using code doesn't have to be aware of this issue.
17#
18# In addition, the functions work with respect to the machine id,
19# since they manipulate a security group permanently associated with
20# the EC2 provided machine, and the machine must be launched into this
21# security group. This security group, per the above
22# `_get_machine_group_name`, embeds the machine id, eg
23# ensemble-moon-42. Ideally, this would not be the case. See the
24# comments associated with the merge proposal of
25# https://code.launchpad.net/~jimbaker/ensemble/expose-provider-ec2/
26
27
28@inlineCallbacks
29def open_provider_port(provider, machine, machine_id, port, protocol):
30 """Authorize `port`/`proto` for the machine security group."""
31 try:
32 yield provider.ec2.authorize_security_group(
33 _get_machine_group_name(provider, machine_id),
34 ip_protocol=protocol,
35 from_port=str(port), to_port=str(port),
36 cidr_ip="0.0.0.0/0")
37 log.debug("Opened %s/%s on provider machine %r",
38 port, protocol, machine.instance_id)
39 except EC2Error, e:
40 raise ProviderInteractionError(
41 "EC2 error when attempting to open %s/%s on machine %s: %s" % (
42 port, protocol, machine.instance_id, e))
43
44
45@inlineCallbacks
46def close_provider_port(provider, machine, machine_id, port, protocol):
47 """Revoke `port`/`proto` for the machine security group."""
48 try:
49 yield provider.ec2.revoke_security_group(
50 _get_machine_group_name(provider, machine_id),
51 ip_protocol=protocol,
52 from_port=str(port), to_port=str(port),
53 cidr_ip="0.0.0.0/0")
54 log.debug("Closed %s/%s on provider machine %r",
55 port, protocol, machine.instance_id)
56 except EC2Error, e:
57 raise ProviderInteractionError(
58 "EC2 error when attempting to close %s/%s on machine %s: %s" % (
59 port, protocol, machine.instance_id, e))
60
61
62@inlineCallbacks
63def get_provider_opened_ports(provider, machine, machine_id):
64 """Gets the opened ports for `machine`.
65
66 Retrieves the IP permissions associated with the machine
67 security group, then parses them to return a set of (port,
68 proto) pairs.
69 """
70 try:
71 security_groups = yield provider.ec2.describe_security_groups(
72 _get_machine_group_name(provider, machine_id))
73 except EC2Error, e:
74 raise ProviderInteractionError(
75 "EC2 error when attempting to get opened ports "
76 "on machine %s: %s" % (
77 machine.instance_id, e))
78
79 opened_ports = set() # made up of (port, protocol) pairs
80 for ip_permission in security_groups[0].allowed_ips:
81 if ip_permission.cidr_ip != "0.0.0.0/0":
82 continue
83 from_port = int(ip_permission.from_port)
84 to_port = int(ip_permission.to_port)
85 if from_port == to_port:
86 # Only return ports that are individually opened. We
87 # ignore multi-port ranges, since they are set outside of
88 # Ensemble (at this time at least)
89 opened_ports.add((from_port, ip_permission.ip_protocol))
90 returnValue(opened_ports)
091
=== modified file 'ensemble/providers/ec2/tests/common.py'
--- ensemble/providers/ec2/tests/common.py 2011-07-27 10:35:59 +0000
+++ ensemble/providers/ec2/tests/common.py 2011-08-10 18:22:55 +0000
@@ -5,6 +5,7 @@
5from txaws.s3.client import S3Client5from txaws.s3.client import S3Client
6from txaws.s3.exception import S3Error6from txaws.s3.exception import S3Error
7from txaws.ec2.client import EC2Client7from txaws.ec2.client import EC2Client
8from txaws.ec2.exception import EC2Error
8from txaws.ec2.model import Instance, SecurityGroup9from txaws.ec2.model import Instance, SecurityGroup
910
10from ensemble.lib.mocker import KWARGS, MATCH11from ensemble.lib.mocker import KWARGS, MATCH
@@ -32,6 +33,29 @@
32 """33 """
33 return MachineProvider(self.env_name, self.get_config())34 return MachineProvider(self.env_name, self.get_config())
3435
36 def get_ec2_error(self, entity_id,
37 format="The instance ID %r does not exist",
38 code=503):
39 """Make a representative EC2Error for `entity_id`, eg AWS instance_id.
40
41 This error is paired with `get_wrapped_ec2_text` below. The
42 default format represents a fairly common error seen in
43 working with EC2. There are others."""
44 message = format % entity_id
45 return EC2Error(
46 "<error><Code>1</Code><Message>%s</Message></error>" % message,
47 code)
48
49 def get_wrapped_ec2_error_text(self, entity_id, reason,
50 format="The instance ID %r does not exist"):
51 """By convention, `EC2Error` is wrapped as a `ProviderError`"""
52 message = format % entity_id
53 return (
54 "ProviderError: Interaction with machine provider failed: "
55 "\"EC2 error when attempting to %s %s: "
56 "Error Message: %s\"" % (
57 reason, entity_id, message))
58
35 def setUp(self):59 def setUp(self):
36 # mock out the aws services60 # mock out the aws services
37 service_factory = self.mocker.replace(61 service_factory = self.mocker.replace(
@@ -95,33 +119,24 @@
95 source_group_owner_id="123")119 source_group_owner_id="123")
96 self.mocker.result(succeed(True))120 self.mocker.result(succeed(True))
97121
98 self.ec2.authorize_security_group(122 def _mock_create_machine_group(self, machine_id):
99 group_name,123 machine_group_name = "ensemble-%s-%s" % (self.env_name, machine_id)
100 ip_protocol="tcp",124 self.ec2.create_security_group(
101 from_port="1",125 machine_group_name, "Ensemble group for %s machine %s" % (
102 to_port="2180",126 self.env_name, machine_id))
103 cidr_ip="0.0.0.0/0")127 self.mocker.result(succeed(True))
104 self.ec2.authorize_security_group(128
105 group_name,129 def _mock_delete_machine_group(self, machine_id):
106 ip_protocol="tcp",130 machine_group_name = "ensemble-%s-%s" % (self.env_name, machine_id)
107 from_port="2182",131 self.ec2.delete_security_group(machine_group_name)
108 to_port="65535",132 self.mocker.result(succeed(True))
109 cidr_ip="0.0.0.0/0")133
110134 def _mock_delete_machine_group_was_deleted(self, machine_id):
111 self.ec2.authorize_security_group(135 machine_group_name = "ensemble-%s-%s" % (self.env_name, machine_id)
112 group_name,136 self.ec2.delete_security_group(machine_group_name)
113 ip_protocol="udp",137 self.mocker.result(fail(self.get_ec2_error(
114 from_port="1",138 machine_group_name,
115 to_port="2180",139 "There are active instances using security group %r")))
116 cidr_ip="0.0.0.0/0")
117 self.ec2.authorize_security_group(
118 group_name,
119 ip_protocol="udp",
120 from_port="2182",
121 to_port="65535",
122 cidr_ip="0.0.0.0/0")
123
124 self.mocker.result(succeed(True))
125140
126 def _mock_get_zookeeper_hosts(self, hosts=None):141 def _mock_get_zookeeper_hosts(self, hosts=None):
127 """142 """
@@ -152,5 +167,3 @@
152 # connect grabs the first host of a set.167 # connect grabs the first host of a set.
153 self.ec2.describe_instances(hosts[0].instance_id)168 self.ec2.describe_instances(hosts[0].instance_id)
154 self.mocker.result(succeed([hosts[0]]))169 self.mocker.result(succeed([hosts[0]]))
155
156
157170
=== modified file 'ensemble/providers/ec2/tests/test_accessor.py'
--- ensemble/providers/ec2/tests/test_accessor.py 2011-01-26 23:39:35 +0000
+++ ensemble/providers/ec2/tests/test_accessor.py 2011-08-10 18:22:55 +0000
@@ -1,4 +1,4 @@
1from twisted.internet.defer import succeed1from twisted.internet.defer import fail, succeed
2from txaws.ec2.model import Instance, Reservation2from txaws.ec2.model import Instance, Reservation
33
4from ensemble.errors import ProviderInteractionError4from ensemble.errors import ProviderInteractionError
@@ -54,7 +54,29 @@
5454
55 def validate_error(error):55 def validate_error(error):
56 self.assertIn(56 self.assertIn(
57 "Machine (id: 'i-foobar') was not found",57 "EC2 instance i-foobar was not found",
58 str(error))
59
60 d.addCallback(validate_error)
61 return d
62
63 def test_instance_not_found(self):
64 """Verify that if the instance is not known to EC2,
65 raises ProviderInteractionError.
66 """
67 self.ec2.describe_instances("i-foobar")
68 self.mocker.result(fail(self.get_ec2_error("i-foobar")))
69 self.mocker.replay()
70
71 provider = self.get_provider()
72
73 d = self.assertFailure(
74 provider.get_machine("i-foobar"),
75 ProviderInteractionError)
76
77 def validate_error(error):
78 self.assertIn(
79 "The instance ID \'i-foobar\' does not exist",
58 str(error))80 str(error))
5981
60 d.addCallback(validate_error)82 d.addCallback(validate_error)
6183
=== modified file 'ensemble/providers/ec2/tests/test_bootstrap.py'
--- ensemble/providers/ec2/tests/test_bootstrap.py 2011-08-04 09:50:57 +0000
+++ ensemble/providers/ec2/tests/test_bootstrap.py 2011-08-10 18:22:55 +0000
@@ -35,7 +35,7 @@
35 MATCH(match_string))35 MATCH(match_string))
36 self.mocker.result(succeed(True))36 self.mocker.result(succeed(True))
3737
38 def _mock_launch(self):38 def _mock_launch(self, machine_id):
39 """Mock launching a bootstrap machine on ec2."""39 """Mock launching a bootstrap machine on ec2."""
40 credentials = "admin:%s" % self.get_config()["admin-secret"]40 credentials = "admin:%s" % self.get_config()["admin-secret"]
41 admin_identity = make_identity(credentials)41 admin_identity = make_identity(credentials)
@@ -81,7 +81,9 @@
81 instance_type="m1.small",81 instance_type="m1.small",
82 max_count=1,82 max_count=1,
83 min_count=1,83 min_count=1,
84 security_groups=["ensemble-%s" % self.env_name],84 security_groups=[
85 "%s-%s" % ("ensemble", self.env_name),
86 "%s-%s-%s" % ("ensemble", self.env_name, machine_id)],
85 user_data=MATCH(verify_user_data))87 user_data=MATCH(verify_user_data))
8688
87 def test_launch_bootstrap(self):89 def test_launch_bootstrap(self):
@@ -95,8 +97,9 @@
95 self.ec2.describe_security_groups()97 self.ec2.describe_security_groups()
96 self.mocker.result(succeed([]))98 self.mocker.result(succeed([]))
97 self._mock_create_group()99 self._mock_create_group()
100 self._mock_create_machine_group(0)
98 self._mock_launch_utils(region="us-east-1")101 self._mock_launch_utils(region="us-east-1")
99 self._mock_launch()102 self._mock_launch(0)
100 self.mocker.result(succeed([]))103 self.mocker.result(succeed([]))
101 self._mock_save()104 self._mock_save()
102 self.mocker.replay()105 self.mocker.replay()
@@ -124,8 +127,9 @@
124 self.ec2.describe_security_groups()127 self.ec2.describe_security_groups()
125 self.mocker.result(succeed([128 self.mocker.result(succeed([
126 SecurityGroup("ensemble-%s" % self.env_name, "")]))129 SecurityGroup("ensemble-%s" % self.env_name, "")]))
130 self._mock_create_machine_group(0)
127 self._mock_launch_utils(region="us-east-1")131 self._mock_launch_utils(region="us-east-1")
128 self._mock_launch()132 self._mock_launch(0)
129 self.mocker.result(succeed([]))133 self.mocker.result(succeed([]))
130 self._mock_save()134 self._mock_save()
131 self.mocker.replay()135 self.mocker.replay()
@@ -201,8 +205,9 @@
201 self.ec2.describe_security_groups()205 self.ec2.describe_security_groups()
202 self.mocker.result(succeed([206 self.mocker.result(succeed([
203 SecurityGroup("ensemble-%s" % self.env_name, "")]))207 SecurityGroup("ensemble-%s" % self.env_name, "")]))
208 self._mock_create_machine_group(0)
204 self._mock_launch_utils(region="us-east-1")209 self._mock_launch_utils(region="us-east-1")
205 self._mock_launch()210 self._mock_launch(0)
206 self.mocker.result(succeed(instances))211 self.mocker.result(succeed(instances))
207 self._mock_save()212 self._mock_save()
208 self.mocker.replay()213 self.mocker.replay()
@@ -229,8 +234,9 @@
229 self.ec2.describe_security_groups()234 self.ec2.describe_security_groups()
230 self.mocker.result(succeed([235 self.mocker.result(succeed([
231 SecurityGroup("ensemble-%s" % self.env_name, "")]))236 SecurityGroup("ensemble-%s" % self.env_name, "")]))
237 self._mock_create_machine_group(0)
232 self._mock_launch_utils(region="us-east-1")238 self._mock_launch_utils(region="us-east-1")
233 self._mock_launch()239 self._mock_launch(0)
234 self.mocker.result(succeed(instances))240 self.mocker.result(succeed(instances))
235 self._mock_save()241 self._mock_save()
236 self.mocker.replay()242 self.mocker.replay()
237243
=== modified file 'ensemble/providers/ec2/tests/test_files.py'
--- ensemble/providers/ec2/tests/test_files.py 2011-08-01 20:25:56 +0000
+++ ensemble/providers/ec2/tests/test_files.py 2011-08-10 18:22:55 +0000
@@ -100,7 +100,7 @@
100100
101 def test_put_file_slightly_wrong_error(self):101 def test_put_file_slightly_wrong_error(self):
102 error = S3Error("<ignored/>", 404)102 error = S3Error("<ignored/>", 404)
103 error.errors = [{"Code": "NoSuchKey"}] # note total nonsense103 error.errors = [{"Code": "NoSuchKey"}] # note total nonsense
104 return self.verify_strange_put_error(error)104 return self.verify_strange_put_error(error)
105105
106 def test_put_file_unknown_error(self):106 def test_put_file_unknown_error(self):
107107
=== modified file 'ensemble/providers/ec2/tests/test_launch.py'
--- ensemble/providers/ec2/tests/test_launch.py 2011-08-02 09:15:47 +0000
+++ ensemble/providers/ec2/tests/test_launch.py 2011-08-10 18:22:55 +0000
@@ -1,10 +1,10 @@
1from yaml import load1from yaml import load
22
3from twisted.internet.defer import succeed3from twisted.internet.defer import inlineCallbacks, succeed
44
5from txaws.ec2.model import Instance5from txaws.ec2.model import Instance, SecurityGroup
66
7from ensemble.errors import EnvironmentNotFound7from ensemble.errors import EnvironmentNotFound, ProviderInteractionError
8from ensemble.providers.common.launch import (8from ensemble.providers.common.launch import (
9 DEFAULT_REPOSITORIES, DEFAULT_PACKAGES)9 DEFAULT_REPOSITORIES, DEFAULT_PACKAGES)
10from ensemble.providers.ec2.machine import EC2ProviderMachine10from ensemble.providers.ec2.machine import EC2ProviderMachine
@@ -17,7 +17,7 @@
1717
18class EC2MachineLaunchTest(EC2TestMixin, EC2MachineLaunchMixin, TestCase):18class EC2MachineLaunchTest(EC2TestMixin, EC2MachineLaunchMixin, TestCase):
1919
20 def _mock_launch(self, instance=None, custom_verify=None):20 def _mock_launch(self, instance=None, custom_verify=None, machine_id=None):
2121
22 def verify_user_data(data):22 def verify_user_data(data):
23 lines = data.split("\n")23 lines = data.split("\n")
@@ -37,7 +37,9 @@
37 instance_type="m1.small",37 instance_type="m1.small",
38 max_count=1,38 max_count=1,
39 min_count=1,39 min_count=1,
40 security_groups=["%s-%s" % ("ensemble", self.env_name)],40 security_groups=[
41 "%s-%s" % ("ensemble", self.env_name),
42 "%s-%s-%s" % ("ensemble", self.env_name, machine_id)],
41 user_data=MATCH(verify_user_data))43 user_data=MATCH(verify_user_data))
4244
43 if instance:45 if instance:
@@ -45,6 +47,7 @@
45 else:47 else:
46 self.mocker.result(succeed([]))48 self.mocker.result(succeed([]))
4749
50 @inlineCallbacks
48 def test_provider_launch(self):51 def test_provider_launch(self):
49 """52 """
50 The provider can be used to launch a machine with a minimal set of53 The provider can be used to launch a machine with a minimal set of
@@ -55,27 +58,101 @@
55 self.ec2.describe_security_groups()58 self.ec2.describe_security_groups()
56 self.mocker.result(succeed([]))59 self.mocker.result(succeed([]))
57 self._mock_create_group()60 self._mock_create_group()
58 self._mock_launch_utils(region="us-east-1")61 self._mock_create_machine_group("machine-1")
59 self._mock_get_zookeeper_hosts()62 self._mock_launch_utils(region="us-east-1")
60 self._mock_launch(instance)63 self._mock_get_zookeeper_hosts()
61 self.mocker.replay()64 self._mock_launch(instance, machine_id="machine-1")
6265 self.mocker.replay()
63 def verify_result(result):66
64 self.assertEqual(len(result), 1)67 provider = self.get_provider()
65 result = result.pop()68 provided_machines = yield provider.start_machine(
66 self.assertTrue(isinstance(result, EC2ProviderMachine))69 {"machine-id": "machine-1"})
67 self.assertEqual(result.instance_id, instance.instance_id)70 self.assertEqual(len(provided_machines), 1)
6871 self.assertTrue(isinstance(provided_machines[0], EC2ProviderMachine))
69 provider = self.get_provider()72 self.assertEqual(
70 d = provider.start_machine({"machine-id": "machine-1"})73 provided_machines[0].instance_id, instance.instance_id)
71 d.addCallback(verify_result)74
72 return d75 @inlineCallbacks
76 def test_provider_launch_existing_security_group(self):
77 """Verify that the launch works if the env security group exists"""
78 instance = Instance("i-foobar", "running", dns_name="x1.example.com")
79 security_group = SecurityGroup("ensemble-moon", "some description")
80
81 self.ec2.describe_security_groups()
82 self.mocker.result(succeed([security_group]))
83 self._mock_create_machine_group("machine-1")
84 self._mock_launch_utils(region="us-east-1")
85 self._mock_get_zookeeper_hosts()
86 self._mock_launch(instance, machine_id="machine-1")
87 self.mocker.replay()
88
89 provider = self.get_provider()
90 provided_machines = yield provider.start_machine(
91 {"machine-id": "machine-1"})
92 self.assertEqual(len(provided_machines), 1)
93 self.assertTrue(isinstance(provided_machines[0], EC2ProviderMachine))
94 self.assertEqual(
95 provided_machines[0].instance_id, instance.instance_id)
96
97 @inlineCallbacks
98 def test_provider_launch_existing_machine_security_group(self):
99 """Verify that the launch works if the machine security group exists"""
100 instance = Instance("i-foobar", "running", dns_name="x1.example.com")
101 machine_group = SecurityGroup(
102 "ensemble-moon-machine-1", "some description")
103
104 self.ec2.describe_security_groups()
105 self.mocker.result(succeed([machine_group]))
106 self._mock_create_group()
107 self._mock_delete_machine_group("machine-1") # delete existing sg
108 self._mock_create_machine_group("machine-1") # then recreate
109 self._mock_launch_utils(region="us-east-1")
110 self._mock_get_zookeeper_hosts()
111 self._mock_launch(instance, machine_id="machine-1")
112 self.mocker.replay()
113
114 provider = self.get_provider()
115 provided_machines = yield provider.start_machine(
116 {"machine-id": "machine-1"})
117 self.assertEqual(len(provided_machines), 1)
118 self.assertTrue(isinstance(provided_machines[0], EC2ProviderMachine))
119 self.assertEqual(
120 provided_machines[0].instance_id, instance.instance_id)
121
122 @inlineCallbacks
123 def test_provider_launch_existing_machine_security_group_is_active(self):
124 """Verify launch fails properly if the machine group is stil active.
125
126 This condition occurs when there is a corresponding machine in
127 that security group, generally because it is still shutting
128 down."""
129 machine_group = SecurityGroup(
130 "ensemble-moon-machine-1", "some description")
131
132 self.ec2.describe_security_groups()
133 self.mocker.result(succeed([machine_group]))
134 self._mock_create_group()
135 self._mock_delete_machine_group_was_deleted("machine-1") # sg is gone!
136 self._mock_get_zookeeper_hosts()
137 self.mocker.replay()
138
139 provider = self.get_provider()
140 ex = yield self.assertFailure(
141 provider.start_machine({"machine-id": "machine-1"}),
142 ProviderInteractionError)
143 self.assertEqual(
144 str(ex),
145 self.get_wrapped_ec2_error_text(
146 "ensemble-moon-machine-1", "delete security group",
147 "There are active instances using security group %r"
148 ))
73149
74 def test_provider_type_machine_variable(self):150 def test_provider_type_machine_variable(self):
75 """The provider type is available via cloud-init."""151 """The provider type is available via cloud-init."""
76 self.ec2.describe_security_groups()152 self.ec2.describe_security_groups()
77 self.mocker.result(succeed([]))153 self.mocker.result(succeed([]))
78 self._mock_create_group()154 self._mock_create_group()
155 self._mock_create_machine_group("machine-1")
79 self._mock_launch_utils(region="us-east-1")156 self._mock_launch_utils(region="us-east-1")
80 self._mock_get_zookeeper_hosts()157 self._mock_get_zookeeper_hosts()
81158
@@ -83,7 +160,8 @@
83 self.assertEqual(160 self.assertEqual(
84 data["machine-data"]["ensemble-provider-type"], "ec2")161 data["machine-data"]["ensemble-provider-type"], "ec2")
85162
86 self._mock_launch(custom_verify=verify_provider_type)163 self._mock_launch(custom_verify=verify_provider_type,
164 machine_id="machine-1")
87 self.mocker.replay()165 self.mocker.replay()
88166
89 provider = self.get_provider()167 provider = self.get_provider()
@@ -128,6 +206,7 @@
128 self.ec2.describe_security_groups()206 self.ec2.describe_security_groups()
129 self.mocker.result(succeed([]))207 self.mocker.result(succeed([]))
130 self._mock_create_group()208 self._mock_create_group()
209 self._mock_create_machine_group("machine-1")
131 self._mock_launch_utils(region="us-east-1")210 self._mock_launch_utils(region="us-east-1")
132 self._mock_get_zookeeper_hosts()211 self._mock_get_zookeeper_hosts()
133212
@@ -139,7 +218,8 @@
139 "--pidfile=/var/run/ensemble/machine-agent.pid")218 "--pidfile=/var/run/ensemble/machine-agent.pid")
140 self.assertEqual(script, data["runcmd"][-1])219 self.assertEqual(script, data["runcmd"][-1])
141220
142 self._mock_launch(custom_verify=verify_machine_agent)221 self._mock_launch(custom_verify=verify_machine_agent,
222 machine_id="machine-1")
143 self.mocker.replay()223 self.mocker.replay()
144224
145 provider = self.get_provider()225 provider = self.get_provider()
@@ -151,9 +231,10 @@
151 self.ec2.describe_security_groups()231 self.ec2.describe_security_groups()
152 self.mocker.result(succeed([]))232 self.mocker.result(succeed([]))
153 self._mock_create_group()233 self._mock_create_group()
234 self._mock_create_machine_group("machine-1")
154 self._mock_launch_utils(**get_ami_kwargs)235 self._mock_launch_utils(**get_ami_kwargs)
155 self._mock_get_zookeeper_hosts()236 self._mock_get_zookeeper_hosts()
156 self._mock_launch(instance)237 self._mock_launch(instance, machine_id="machine-1")
157238
158 def test_launch_options_known_ami(self):239 def test_launch_options_known_ami(self):
159 self._mock_launch_with_ami_params({})240 self._mock_launch_with_ami_params({})
160241
=== added file 'ensemble/providers/ec2/tests/test_securitygroup.py'
--- ensemble/providers/ec2/tests/test_securitygroup.py 1970-01-01 00:00:00 +0000
+++ ensemble/providers/ec2/tests/test_securitygroup.py 2011-08-10 18:22:55 +0000
@@ -0,0 +1,129 @@
1import logging
2
3from twisted.internet.defer import fail, succeed, inlineCallbacks
4from txaws.ec2.model import IPPermission, SecurityGroup
5
6from ensemble.errors import ProviderInteractionError
7from ensemble.lib.testing import TestCase
8from ensemble.machine import ProviderMachine
9from ensemble.providers.ec2.securitygroup import (
10 open_provider_port, close_provider_port, get_provider_opened_ports)
11
12from .common import EC2TestMixin
13
14
15class EC2SecurityGroupTest(EC2TestMixin, TestCase):
16
17 @inlineCallbacks
18 def test_open_provider_port(self):
19 """Verify open port op will use the correct EC2 API."""
20 log = self.capture_logging("ensemble.ec2", level=logging.DEBUG)
21 machine = ProviderMachine("i-foobar", "x1.example.com")
22 self.ec2.authorize_security_group(
23 "ensemble-moon-machine-1", ip_protocol="tcp", from_port="80",
24 to_port="80", cidr_ip="0.0.0.0/0")
25 self.mocker.result(succeed(True))
26 self.mocker.replay()
27
28 provider = self.get_provider()
29 yield open_provider_port(provider, machine, "machine-1", 80, "tcp")
30 self.assertIn(
31 "Opened 80/tcp on provider machine 'i-foobar'",
32 log.getvalue())
33
34 @inlineCallbacks
35 def test_close_provider_port(self):
36 """Verify close port op will use the correct EC2 API."""
37 log = self.capture_logging("ensemble.ec2", level=logging.DEBUG)
38 machine = ProviderMachine("i-foobar", "x1.example.com")
39 self.ec2.revoke_security_group(
40 "ensemble-moon-machine-1", ip_protocol="tcp", from_port="80",
41 to_port="80", cidr_ip="0.0.0.0/0")
42 self.mocker.result(succeed(True))
43 self.mocker.replay()
44
45 provider = self.get_provider()
46 yield close_provider_port(provider, machine, "machine-1", 80, "tcp")
47 self.assertIn(
48 "Closed 80/tcp on provider machine 'i-foobar'",
49 log.getvalue())
50
51 @inlineCallbacks
52 def test_get_provider_opened_ports(self):
53 """Verify correct parse of IP perms from describe_security_group."""
54 self.ec2.describe_security_groups("ensemble-moon-machine-1")
55 self.mocker.result(succeed([
56 SecurityGroup(
57 "ensemble-%s-machine-1" % self.env_name,
58 "a security group name",
59 ips=[
60 IPPermission("udp", "53", "53", "0.0.0.0/0"),
61 IPPermission("tcp", "80", "80", "0.0.0.0/0"),
62 # The range 8080-8082 will be ignored
63 IPPermission("tcp", "8080", "8082", "0.0.0.0/0"),
64 # Ignore permissions that are not 0.0.0.0/0
65 IPPermission("tcp", "443", "443", "10.1.2.3")
66 ])]))
67 self.mocker.replay()
68
69 provider = self.get_provider()
70 machine = ProviderMachine(
71 "i-foobar", "x1.example.com")
72 opened_ports = yield get_provider_opened_ports(
73 provider, machine, "machine-1")
74 self.assertEqual(opened_ports, set([(53, "udp"), (80, "tcp")]))
75
76 @inlineCallbacks
77 def test_open_provider_port_unknown_instance(self):
78 """Verify open port op will use the correct EC2 API."""
79 machine = ProviderMachine("i-foobar", "x1.example.com")
80 self.ec2.authorize_security_group(
81 "ensemble-moon-machine-1", ip_protocol="tcp", from_port="80",
82 to_port="80", cidr_ip="0.0.0.0/0")
83 self.mocker.result(fail(self.get_ec2_error("i-foobar")))
84 self.mocker.replay()
85
86 provider = self.get_provider()
87 ex = yield self.assertFailure(
88 open_provider_port(provider, machine, "machine-1", 80, "tcp"),
89 ProviderInteractionError)
90 self.assertEqual(
91 str(ex),
92 self.get_wrapped_ec2_error_text(
93 "i-foobar", "open 80/tcp on machine"))
94
95 @inlineCallbacks
96 def test_close_provider_port_unknown_instance(self):
97 """Verify open port op will use the correct EC2 API."""
98 machine = ProviderMachine("i-foobar", "x1.example.com")
99 self.ec2.revoke_security_group(
100 "ensemble-moon-machine-1", ip_protocol="tcp", from_port="80",
101 to_port="80", cidr_ip="0.0.0.0/0")
102 self.mocker.result(fail(self.get_ec2_error("i-foobar")))
103 self.mocker.replay()
104
105 provider = self.get_provider()
106 ex = yield self.assertFailure(
107 close_provider_port(provider, machine, "machine-1", 80, "tcp"),
108 ProviderInteractionError)
109 self.assertEqual(
110 str(ex),
111 self.get_wrapped_ec2_error_text(
112 "i-foobar", "close 80/tcp on machine"))
113
114 @inlineCallbacks
115 def test_get_provider_opened_ports_unknown_instance(self):
116 """Verify open port op will use the correct EC2 API."""
117 self.ec2.describe_security_groups("ensemble-moon-machine-1")
118 self.mocker.result(fail(self.get_ec2_error("i-foobar")))
119 self.mocker.replay()
120
121 provider = self.get_provider()
122 machine = ProviderMachine("i-foobar", "x1.example.com")
123 ex = yield self.assertFailure(
124 get_provider_opened_ports(provider, machine, "machine-1"),
125 ProviderInteractionError)
126 self.assertEqual(
127 str(ex),
128 self.get_wrapped_ec2_error_text(
129 "i-foobar", "get opened ports on machine"))
0130
=== modified file 'ensemble/providers/orchestra/launch.py'
--- ensemble/providers/orchestra/launch.py 2011-08-09 21:19:13 +0000
+++ ensemble/providers/orchestra/launch.py 2011-08-10 18:22:55 +0000
@@ -62,7 +62,7 @@
62class OrchestraLaunchMachine(LaunchMachine):62class OrchestraLaunchMachine(LaunchMachine):
6363
64 @inlineCallbacks64 @inlineCallbacks
65 def start_machine(self, variables):65 def start_machine(self, variables, machine_id):
66 cobbler = self._provider.cobbler66 cobbler = self._provider.cobbler
67 instance_id = yield cobbler.acquire_system()67 instance_id = yield cobbler.acquire_system()
68 ks_meta = self._build_ks_meta(instance_id, variables)68 ks_meta = self._build_ks_meta(instance_id, variables)
6969
=== modified file 'ensemble/providers/orchestra/tests/common.py'
--- ensemble/providers/orchestra/tests/common.py 2011-08-09 21:19:13 +0000
+++ ensemble/providers/orchestra/tests/common.py 2011-08-10 18:22:55 +0000
@@ -153,4 +153,3 @@
153 self.mocker.result(succeed([name]))153 self.mocker.result(succeed([name]))
154 self.proxy_m.callRemote("get_system", name)154 self.proxy_m.callRemote("get_system", name)
155 self.mocker.result(succeed({"name": name, "uid": uid}))155 self.mocker.result(succeed({"name": name, "uid": uid}))
156
157156
=== modified file 'ensemble/providers/tests/test_dummy.py'
--- ensemble/providers/tests/test_dummy.py 2011-07-31 00:20:08 +0000
+++ ensemble/providers/tests/test_dummy.py 2011-08-10 18:22:55 +0000
@@ -57,7 +57,8 @@
5757
58 @inlineCallbacks58 @inlineCallbacks
59 def test_start_machine_accepts_machine_data(self):59 def test_start_machine_accepts_machine_data(self):
60 machines = yield self.provider.start_machine({"machine-id": 0, "a": 1})60 machines = yield self.provider.start_machine(
61 {"machine-id": 0, "a": 1})
61 self.assertEqual(len(machines), 1)62 self.assertEqual(len(machines), 1)
62 self.assertTrue(isinstance(machines[0], ProviderMachine))63 self.assertTrue(isinstance(machines[0], ProviderMachine))
6364
@@ -141,13 +142,13 @@
141 """Verifies dummy provider properly works with ports."""142 """Verifies dummy provider properly works with ports."""
142 machines = yield self.provider.start_machine({"machine-id": 0})143 machines = yield self.provider.start_machine({"machine-id": 0})
143 machine = machines[0]144 machine = machines[0]
144 yield self.provider.open_port(machine, 25, "tcp")145 yield self.provider.open_port(machine, 0, 25, "tcp")
145 yield self.provider.open_port(machine, 80)146 yield self.provider.open_port(machine, 0, 80)
146 yield self.provider.open_port(machine, 53, "udp")147 yield self.provider.open_port(machine, 0, 53, "udp")
147 yield self.provider.open_port(machine, 443, "tcp")148 yield self.provider.open_port(machine, 0, 443, "tcp")
148 yield self.provider.close_port(machine, 25)149 yield self.provider.close_port(machine, 0, 25)
149 yield self.provider.close_port(machine, 25) # ignored150 yield self.provider.close_port(machine, 0, 25) # ignored
150 exposed_ports = yield self.provider.get_opened_ports(machine)151 exposed_ports = yield self.provider.get_opened_ports(machine, 0)
151 self.assertEqual(exposed_ports,152 self.assertEqual(exposed_ports,
152 set([(53, 'udp'), (80, 'tcp'), (443, 'tcp')]))153 set([(53, 'udp'), (80, 'tcp'), (443, 'tcp')]))
153154
154155
=== modified file 'examples/wordpress/hooks/db-relation-changed'
--- examples/wordpress/hooks/db-relation-changed 2011-05-03 08:54:13 +0000
+++ examples/wordpress/hooks/db-relation-changed 2011-08-10 18:22:55 +0000
@@ -98,3 +98,6 @@
98# Restart apache98# Restart apache
99ensemble-log "Restarting apache2 service"99ensemble-log "Restarting apache2 service"
100/etc/init.d/apache2 restart100/etc/init.d/apache2 restart
101
102# Make it publicly visible, once the wordpress service is exposed
103open-port 80/tcp

Subscribers

People subscribed via source and target branches

to status/vote changes: