Fix merge conflicts and add EC2 Instance delete. Closes #576.
This commit is contained in:
parent
0b24c6be57
commit
a600deb96a
@ -3,6 +3,7 @@ from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping
|
|||||||
from moto.core import BaseBackend
|
from moto.core import BaseBackend
|
||||||
from moto.ec2 import ec2_backends
|
from moto.ec2 import ec2_backends
|
||||||
from moto.elb import elb_backends
|
from moto.elb import elb_backends
|
||||||
|
from moto.elb.exceptions import LoadBalancerNotFoundError
|
||||||
|
|
||||||
# http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/AS_Concepts.html#Cooldown
|
# http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/AS_Concepts.html#Cooldown
|
||||||
DEFAULT_COOLDOWN = 300
|
DEFAULT_COOLDOWN = 300
|
||||||
@ -80,6 +81,19 @@ class FakeLaunchConfiguration(object):
|
|||||||
)
|
)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
|
||||||
|
cls.delete_from_cloudformation_json(original_resource.name, cloudformation_json, region_name)
|
||||||
|
return cls.create_from_cloudformation_json(new_resource_name, cloudformation_json, region_name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
|
backend = autoscaling_backends[region_name]
|
||||||
|
try:
|
||||||
|
backend.delete_launch_configuration(resource_name)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
def delete(self, region_name):
|
def delete(self, region_name):
|
||||||
backend = autoscaling_backends[region_name]
|
backend = autoscaling_backends[region_name]
|
||||||
backend.delete_launch_configuration(self.name)
|
backend.delete_launch_configuration(self.name)
|
||||||
@ -171,9 +185,29 @@ class FakeAutoScalingGroup(object):
|
|||||||
)
|
)
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
|
||||||
|
cls.delete_from_cloudformation_json(original_resource.name, cloudformation_json, region_name)
|
||||||
|
return cls.create_from_cloudformation_json(new_resource_name, cloudformation_json, region_name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
|
backend = autoscaling_backends[region_name]
|
||||||
|
try:
|
||||||
|
backend.delete_autoscaling_group(resource_name)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except LoadBalancerNotFoundError:
|
||||||
|
# sometimes the ELB gets modified before the ASG, so just skip over desired capacity
|
||||||
|
backend.autoscaling_groups.pop(resource_name, None)
|
||||||
|
|
||||||
def delete(self, region_name):
|
def delete(self, region_name):
|
||||||
backend = autoscaling_backends[region_name]
|
backend = autoscaling_backends[region_name]
|
||||||
backend.delete_autoscaling_group(self.name)
|
try:
|
||||||
|
backend.delete_autoscaling_group(self.name)
|
||||||
|
except LoadBalancerNotFoundError:
|
||||||
|
# sometimes the ELB gets deleted before the ASG, so just skip over desired capacity
|
||||||
|
backend.autoscaling_groups.pop(self.name, None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def physical_resource_id(self):
|
def physical_resource_id(self):
|
||||||
|
@ -14,7 +14,7 @@ class ValidationError(BadRequest):
|
|||||||
super(ValidationError, self).__init__()
|
super(ValidationError, self).__init__()
|
||||||
self.description = template.render(
|
self.description = template.render(
|
||||||
code="ValidationError",
|
code="ValidationError",
|
||||||
messgae="Stack:{0} does not exist".format(name_or_id),
|
message="Stack:{0} does not exist".format(name_or_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ class MissingParameterError(BadRequest):
|
|||||||
super(MissingParameterError, self).__init__()
|
super(MissingParameterError, self).__init__()
|
||||||
self.description = template.render(
|
self.description = template.render(
|
||||||
code="Missing Parameter",
|
code="Missing Parameter",
|
||||||
messgae="Missing parameter {0}".format(parameter_name),
|
message="Missing parameter {0}".format(parameter_name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,11 +51,11 @@ class FakeStack(object):
|
|||||||
self.template = template
|
self.template = template
|
||||||
self.resource_map.update(json.loads(template))
|
self.resource_map.update(json.loads(template))
|
||||||
self.output_map = self._create_output_map()
|
self.output_map = self._create_output_map()
|
||||||
self.status = 'UPDATE_COMPLETE'
|
self.status = "UPDATE_COMPLETE"
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
self.resource_map.delete()
|
self.resource_map.delete()
|
||||||
self.status = 'DELETE_COMPLETE'
|
self.status = "DELETE_COMPLETE"
|
||||||
|
|
||||||
|
|
||||||
class CloudFormationBackend(BaseBackend):
|
class CloudFormationBackend(BaseBackend):
|
||||||
|
@ -115,7 +115,7 @@ def clean_json(resource_json, resources_map):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
if 'Fn::GetAtt' in resource_json:
|
if 'Fn::GetAtt' in resource_json:
|
||||||
resource = resources_map[resource_json['Fn::GetAtt'][0]]
|
resource = resources_map.get(resource_json['Fn::GetAtt'][0])
|
||||||
if resource is None:
|
if resource is None:
|
||||||
return resource_json
|
return resource_json
|
||||||
try:
|
try:
|
||||||
@ -208,15 +208,22 @@ def parse_and_create_resource(logical_id, resource_json, resources_map, region_n
|
|||||||
|
|
||||||
|
|
||||||
def parse_and_update_resource(logical_id, resource_json, resources_map, region_name):
|
def parse_and_update_resource(logical_id, resource_json, resources_map, region_name):
|
||||||
resource_class, resource_json, resource_name = parse_resource(logical_id, resource_json, resources_map)
|
resource_class, new_resource_json, new_resource_name = parse_resource(logical_id, resource_json, resources_map)
|
||||||
resource = resource_class.update_from_cloudformation_json(resource_name, resource_json, region_name)
|
original_resource = resources_map[logical_id]
|
||||||
return resource
|
new_resource = resource_class.update_from_cloudformation_json(
|
||||||
|
original_resource=original_resource,
|
||||||
|
new_resource_name=new_resource_name,
|
||||||
|
cloudformation_json=new_resource_json,
|
||||||
|
region_name=region_name
|
||||||
|
)
|
||||||
|
new_resource.type = resource_json['Type']
|
||||||
|
new_resource.logical_resource_id = logical_id
|
||||||
|
return new_resource
|
||||||
|
|
||||||
|
|
||||||
def parse_and_delete_resource(logical_id, resource_json, resources_map, region_name):
|
def parse_and_delete_resource(logical_id, resource_json, resources_map, region_name):
|
||||||
resource_class, resource_json, resource_name = parse_resource(logical_id, resource_json, resources_map)
|
resource_class, resource_json, resource_name = parse_resource(logical_id, resource_json, resources_map)
|
||||||
resource_class.delete_from_cloudformation_json(resource_name, resource_json, region_name)
|
resource_class.delete_from_cloudformation_json(resource_name, resource_json, region_name)
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def parse_condition(condition, resources_map, condition_map):
|
def parse_condition(condition, resources_map, condition_map):
|
||||||
@ -289,6 +296,8 @@ class ResourceMap(collections.Mapping):
|
|||||||
return self._parsed_resources[resource_logical_id]
|
return self._parsed_resources[resource_logical_id]
|
||||||
else:
|
else:
|
||||||
resource_json = self._resource_json_map.get(resource_logical_id)
|
resource_json = self._resource_json_map.get(resource_logical_id)
|
||||||
|
if not resource_json:
|
||||||
|
raise KeyError(resource_logical_id)
|
||||||
new_resource = parse_and_create_resource(resource_logical_id, resource_json, self, self._region_name)
|
new_resource = parse_and_create_resource(resource_logical_id, resource_json, self, self._region_name)
|
||||||
self._parsed_resources[resource_logical_id] = new_resource
|
self._parsed_resources[resource_logical_id] = new_resource
|
||||||
return new_resource
|
return new_resource
|
||||||
@ -369,18 +378,40 @@ class ResourceMap(collections.Mapping):
|
|||||||
parse_and_delete_resource(resource_name, resource_json, self, self._region_name)
|
parse_and_delete_resource(resource_name, resource_json, self, self._region_name)
|
||||||
self._parsed_resources.pop(resource_name)
|
self._parsed_resources.pop(resource_name)
|
||||||
|
|
||||||
for resource_name in new_template:
|
resources_to_update = set(name for name in new_template if name in old_template and new_template[name] != old_template[name])
|
||||||
if resource_name in old_template and new_template[resource_name] != old_template[resource_name]:
|
tries = 1
|
||||||
|
while resources_to_update and tries < 5:
|
||||||
|
for resource_name in resources_to_update.copy():
|
||||||
resource_json = new_template[resource_name]
|
resource_json = new_template[resource_name]
|
||||||
|
try:
|
||||||
changed_resource = parse_and_update_resource(resource_name, resource_json, self, self._region_name)
|
changed_resource = parse_and_update_resource(resource_name, resource_json, self, self._region_name)
|
||||||
self._parsed_resources[resource_name] = changed_resource
|
except Exception as e:
|
||||||
|
# skip over dependency violations, and try again in a second pass
|
||||||
|
last_exception = e
|
||||||
|
else:
|
||||||
|
self._parsed_resources[resource_name] = changed_resource
|
||||||
|
resources_to_update.remove(resource_name)
|
||||||
|
tries += 1
|
||||||
|
if tries == 5:
|
||||||
|
raise last_exception
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
for resource in self.resources:
|
remaining_resources = set(self.resources)
|
||||||
parsed_resource = self._parsed_resources.pop(resource)
|
tries = 1
|
||||||
if parsed_resource and hasattr(parsed_resource, 'delete'):
|
while remaining_resources and tries < 5:
|
||||||
parsed_resource.delete(self._region_name)
|
for resource in remaining_resources.copy():
|
||||||
|
parsed_resource = self._parsed_resources.get(resource)
|
||||||
|
if parsed_resource:
|
||||||
|
try:
|
||||||
|
parsed_resource.delete(self._region_name)
|
||||||
|
except Exception as e:
|
||||||
|
# skip over dependency violations, and try again in a second pass
|
||||||
|
last_exception = e
|
||||||
|
else:
|
||||||
|
remaining_resources.remove(resource)
|
||||||
|
tries += 1
|
||||||
|
if tries == 5:
|
||||||
|
raise last_exception
|
||||||
|
|
||||||
|
|
||||||
class OutputMap(collections.Mapping):
|
class OutputMap(collections.Mapping):
|
||||||
|
@ -124,21 +124,24 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
|
|
||||||
def update_stack(self):
|
def update_stack(self):
|
||||||
stack_name = self._get_param('StackName')
|
stack_name = self._get_param('StackName')
|
||||||
stack_body = self._get_param('TemplateBody')
|
if self._get_param('UsePreviousTemplate') == "true":
|
||||||
|
stack_body = self.cloudformation_backend.get_stack(stack_name).template
|
||||||
|
else:
|
||||||
|
stack_body = self._get_param('TemplateBody')
|
||||||
|
|
||||||
stack = self.cloudformation_backend.update_stack(
|
stack = self.cloudformation_backend.update_stack(
|
||||||
name=stack_name,
|
name=stack_name,
|
||||||
template=stack_body,
|
template=stack_body,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.request_json:
|
if self.request_json:
|
||||||
return json.dumps({
|
stack_body = {
|
||||||
'UpdateStackResponse': {
|
'UpdateStackResponse': {
|
||||||
'UpdateStackResult': {
|
'UpdateStackResult': {
|
||||||
'StackId': stack.name,
|
'StackId': stack.name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
return json.dumps(stack_body)
|
||||||
else:
|
else:
|
||||||
template = self.response_template(UPDATE_STACK_RESPONSE_TEMPLATE)
|
template = self.response_template(UPDATE_STACK_RESPONSE_TEMPLATE)
|
||||||
return template.render(stack=stack)
|
return template.render(stack=stack)
|
||||||
@ -310,6 +313,16 @@ GET_TEMPLATE_RESPONSE_TEMPLATE = """<GetTemplateResponse>
|
|||||||
</GetTemplateResponse>"""
|
</GetTemplateResponse>"""
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE_STACK_RESPONSE_TEMPLATE = """<UpdateStackResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
|
||||||
|
<UpdateStackResult>
|
||||||
|
<StackId>{{ stack.stack_id }}</StackId>
|
||||||
|
</UpdateStackResult>
|
||||||
|
<ResponseMetadata>
|
||||||
|
<RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId>
|
||||||
|
</ResponseMetadata>
|
||||||
|
</UpdateStackResponse>
|
||||||
|
"""
|
||||||
|
|
||||||
DELETE_STACK_RESPONSE_TEMPLATE = """<DeleteStackResponse>
|
DELETE_STACK_RESPONSE_TEMPLATE = """<DeleteStackResponse>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
<RequestId>5ccc7dcd-744c-11e5-be70-example</RequestId>
|
<RequestId>5ccc7dcd-744c-11e5-be70-example</RequestId>
|
||||||
|
@ -434,6 +434,9 @@ class Instance(BotoInstance, TaggedEC2Resource):
|
|||||||
self._state_reason = StateReason("Client.UserInitiatedShutdown: User initiated shutdown",
|
self._state_reason = StateReason("Client.UserInitiatedShutdown: User initiated shutdown",
|
||||||
"Client.UserInitiatedShutdown")
|
"Client.UserInitiatedShutdown")
|
||||||
|
|
||||||
|
def delete(self, region):
|
||||||
|
self.terminate()
|
||||||
|
|
||||||
def terminate(self, *args, **kwargs):
|
def terminate(self, *args, **kwargs):
|
||||||
for nic in self.nics.values():
|
for nic in self.nics.values():
|
||||||
nic.stop()
|
nic.stop()
|
||||||
@ -1135,6 +1138,29 @@ class SecurityGroup(TaggedEC2Resource):
|
|||||||
|
|
||||||
return security_group
|
return security_group
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
|
||||||
|
cls._delete_security_group_given_vpc_id(original_resource.name, original_resource.vpc_id, region_name)
|
||||||
|
return cls.create_from_cloudformation_json(new_resource_name, cloudformation_json, region_name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
|
properties = cloudformation_json['Properties']
|
||||||
|
vpc_id = properties.get('VpcId')
|
||||||
|
cls._delete_security_group_given_vpc_id(resource_name, vpc_id, region_name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _delete_security_group_given_vpc_id(cls, resource_name, vpc_id, region_name):
|
||||||
|
ec2_backend = ec2_backends[region_name]
|
||||||
|
security_group = ec2_backend.get_security_group_from_name(resource_name, vpc_id)
|
||||||
|
if security_group:
|
||||||
|
security_group.delete(region_name)
|
||||||
|
|
||||||
|
def delete(self, region_name):
|
||||||
|
''' Not exposed as part of the ELB API - used for CloudFormation. '''
|
||||||
|
backend = ec2_backends[region_name]
|
||||||
|
self.ec2_backend.delete_security_group(group_id=self.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def physical_resource_id(self):
|
def physical_resource_id(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
@ -64,20 +64,21 @@ class FakeLoadBalancer(object):
|
|||||||
self.subnets = subnets or []
|
self.subnets = subnets or []
|
||||||
self.vpc_id = vpc_id or 'vpc-56e10e3d'
|
self.vpc_id = vpc_id or 'vpc-56e10e3d'
|
||||||
self.tags = {}
|
self.tags = {}
|
||||||
|
self.dns_name = "tests.us-east-1.elb.amazonaws.com"
|
||||||
|
|
||||||
for port in ports:
|
for port in ports:
|
||||||
listener = FakeListener(
|
listener = FakeListener(
|
||||||
protocol=port['protocol'],
|
protocol=(port.get('protocol') or port['Protocol']),
|
||||||
load_balancer_port=port['load_balancer_port'],
|
load_balancer_port=(port.get('load_balancer_port') or port['LoadBalancerPort']),
|
||||||
instance_port=port['instance_port'],
|
instance_port=(port.get('instance_port') or port['InstancePort']),
|
||||||
ssl_certificate_id=port.get('sslcertificate_id'),
|
ssl_certificate_id=port.get('sslcertificate_id', port.get('SSLCertificateId')),
|
||||||
)
|
)
|
||||||
self.listeners.append(listener)
|
self.listeners.append(listener)
|
||||||
|
|
||||||
# it is unclear per the AWS documentation as to when or how backend
|
# it is unclear per the AWS documentation as to when or how backend
|
||||||
# information gets set, so let's guess and set it here *shrug*
|
# information gets set, so let's guess and set it here *shrug*
|
||||||
backend = FakeBackend(
|
backend = FakeBackend(
|
||||||
instance_port=port['instance_port'],
|
instance_port=(port.get('instance_port') or port['InstancePort']),
|
||||||
)
|
)
|
||||||
self.backends.append(backend)
|
self.backends.append(backend)
|
||||||
|
|
||||||
@ -88,15 +89,41 @@ class FakeLoadBalancer(object):
|
|||||||
elb_backend = elb_backends[region_name]
|
elb_backend = elb_backends[region_name]
|
||||||
new_elb = elb_backend.create_load_balancer(
|
new_elb = elb_backend.create_load_balancer(
|
||||||
name=properties.get('LoadBalancerName', resource_name),
|
name=properties.get('LoadBalancerName', resource_name),
|
||||||
zones=properties.get('AvailabilityZones'),
|
zones=properties.get('AvailabilityZones', []),
|
||||||
ports=[],
|
ports=properties['Listeners'],
|
||||||
|
scheme=properties.get('Scheme', 'internet-facing'),
|
||||||
)
|
)
|
||||||
|
|
||||||
instance_ids = cloudformation_json.get('Instances', [])
|
instance_ids = properties.get('Instances', [])
|
||||||
for instance_id in instance_ids:
|
for instance_id in instance_ids:
|
||||||
elb_backend.register_instances(new_elb.name, [instance_id])
|
elb_backend.register_instances(new_elb.name, [instance_id])
|
||||||
|
|
||||||
|
health_check = properties.get('HealthCheck')
|
||||||
|
if health_check:
|
||||||
|
elb_backend.configure_health_check(
|
||||||
|
load_balancer_name=new_elb.name,
|
||||||
|
timeout=health_check['Timeout'],
|
||||||
|
healthy_threshold=health_check['HealthyThreshold'],
|
||||||
|
unhealthy_threshold=health_check['UnhealthyThreshold'],
|
||||||
|
interval=health_check['Interval'],
|
||||||
|
target=health_check['Target'],
|
||||||
|
)
|
||||||
|
|
||||||
return new_elb
|
return new_elb
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
|
||||||
|
cls.delete_from_cloudformation_json(original_resource.name, cloudformation_json, region_name)
|
||||||
|
return cls.create_from_cloudformation_json(new_resource_name, cloudformation_json, region_name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
|
elb_backend = elb_backends[region_name]
|
||||||
|
try:
|
||||||
|
elb_backend.delete_load_balancer(resource_name)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def physical_resource_id(self):
|
def physical_resource_id(self):
|
||||||
return self.name
|
return self.name
|
||||||
@ -108,7 +135,7 @@ class FakeLoadBalancer(object):
|
|||||||
elif attribute_name == 'CanonicalHostedZoneNameID':
|
elif attribute_name == 'CanonicalHostedZoneNameID':
|
||||||
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "CanonicalHostedZoneNameID" ]"')
|
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "CanonicalHostedZoneNameID" ]"')
|
||||||
elif attribute_name == 'DNSName':
|
elif attribute_name == 'DNSName':
|
||||||
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "DNSName" ]"')
|
return self.dns_name
|
||||||
elif attribute_name == 'SourceSecurityGroup.GroupName':
|
elif attribute_name == 'SourceSecurityGroup.GroupName':
|
||||||
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "SourceSecurityGroup.GroupName" ]"')
|
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "SourceSecurityGroup.GroupName" ]"')
|
||||||
elif attribute_name == 'SourceSecurityGroup.OwnerAlias':
|
elif attribute_name == 'SourceSecurityGroup.OwnerAlias':
|
||||||
@ -149,6 +176,10 @@ class FakeLoadBalancer(object):
|
|||||||
if key in self.tags:
|
if key in self.tags:
|
||||||
del self.tags[key]
|
del self.tags[key]
|
||||||
|
|
||||||
|
def delete(self, region):
|
||||||
|
''' Not exposed as part of the ELB API - used for CloudFormation. '''
|
||||||
|
elb_backends[region].delete_load_balancer(self.name)
|
||||||
|
|
||||||
|
|
||||||
class ELBBackend(BaseBackend):
|
class ELBBackend(BaseBackend):
|
||||||
|
|
||||||
|
@ -12,8 +12,7 @@ from boto.ec2.elb.policies import (
|
|||||||
|
|
||||||
from moto.core.responses import BaseResponse
|
from moto.core.responses import BaseResponse
|
||||||
from .models import elb_backends
|
from .models import elb_backends
|
||||||
from .exceptions import DuplicateTagKeysError, LoadBalancerNotFoundError, \
|
from .exceptions import DuplicateTagKeysError, LoadBalancerNotFoundError
|
||||||
TooManyTagsError
|
|
||||||
|
|
||||||
|
|
||||||
class ELBResponse(BaseResponse):
|
class ELBResponse(BaseResponse):
|
||||||
@ -29,16 +28,16 @@ class ELBResponse(BaseResponse):
|
|||||||
scheme = self._get_param('Scheme')
|
scheme = self._get_param('Scheme')
|
||||||
subnets = self._get_multi_param("Subnets.member")
|
subnets = self._get_multi_param("Subnets.member")
|
||||||
|
|
||||||
elb = self.elb_backend.create_load_balancer(
|
load_balancer = self.elb_backend.create_load_balancer(
|
||||||
name=load_balancer_name,
|
name=load_balancer_name,
|
||||||
zones=availability_zones,
|
zones=availability_zones,
|
||||||
ports=ports,
|
ports=ports,
|
||||||
scheme=scheme,
|
scheme=scheme,
|
||||||
subnets=subnets,
|
subnets=subnets,
|
||||||
)
|
)
|
||||||
self._add_tags(elb)
|
self._add_tags(load_balancer)
|
||||||
template = self.response_template(CREATE_LOAD_BALANCER_TEMPLATE)
|
template = self.response_template(CREATE_LOAD_BALANCER_TEMPLATE)
|
||||||
return template.render()
|
return template.render(load_balancer=load_balancer)
|
||||||
|
|
||||||
def create_load_balancer_listeners(self):
|
def create_load_balancer_listeners(self):
|
||||||
load_balancer_name = self._get_param('LoadBalancerName')
|
load_balancer_name = self._get_param('LoadBalancerName')
|
||||||
@ -244,7 +243,7 @@ class ELBResponse(BaseResponse):
|
|||||||
load_balancer_name = self._get_param('LoadBalancerNames.member.{0}'.format(number))
|
load_balancer_name = self._get_param('LoadBalancerNames.member.{0}'.format(number))
|
||||||
elb = self.elb_backend.get_load_balancer(load_balancer_name)
|
elb = self.elb_backend.get_load_balancer(load_balancer_name)
|
||||||
if not elb:
|
if not elb:
|
||||||
raise LoadBalancerNotFound(load_balancer_name)
|
raise LoadBalancerNotFoundError(load_balancer_name)
|
||||||
|
|
||||||
key = 'Tag.member.{0}.Key'.format(number)
|
key = 'Tag.member.{0}.Key'.format(number)
|
||||||
for t_key, t_val in self.querystring.items():
|
for t_key, t_val in self.querystring.items():
|
||||||
@ -263,7 +262,7 @@ class ELBResponse(BaseResponse):
|
|||||||
load_balancer_name = self._get_param('LoadBalancerNames.member.{0}'.format(number))
|
load_balancer_name = self._get_param('LoadBalancerNames.member.{0}'.format(number))
|
||||||
elb = self.elb_backend.get_load_balancer(load_balancer_name)
|
elb = self.elb_backend.get_load_balancer(load_balancer_name)
|
||||||
if not elb:
|
if not elb:
|
||||||
raise LoadBalancerNotFound(load_balancer_name)
|
raise LoadBalancerNotFoundError(load_balancer_name)
|
||||||
elbs.append(elb)
|
elbs.append(elb)
|
||||||
|
|
||||||
template = self.response_template(DESCRIBE_TAGS_TEMPLATE)
|
template = self.response_template(DESCRIBE_TAGS_TEMPLATE)
|
||||||
@ -334,7 +333,7 @@ DESCRIBE_TAGS_TEMPLATE = """<DescribeTagsResponse xmlns="http://elasticloadbalan
|
|||||||
|
|
||||||
CREATE_LOAD_BALANCER_TEMPLATE = """<CreateLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
CREATE_LOAD_BALANCER_TEMPLATE = """<CreateLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
||||||
<CreateLoadBalancerResult>
|
<CreateLoadBalancerResult>
|
||||||
<DNSName>tests.us-east-1.elb.amazonaws.com</DNSName>
|
<DNSName>{{ load_balancer.dns_name }}</DNSName>
|
||||||
</CreateLoadBalancerResult>
|
</CreateLoadBalancerResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
<RequestId>1549581b-12b7-11e3-895e-1334aEXAMPLE</RequestId>
|
<RequestId>1549581b-12b7-11e3-895e-1334aEXAMPLE</RequestId>
|
||||||
@ -442,7 +441,7 @@ DESCRIBE_LOAD_BALANCERS_TEMPLATE = """<DescribeLoadBalancersResponse xmlns="http
|
|||||||
<CanonicalHostedZoneName>tests.us-east-1.elb.amazonaws.com</CanonicalHostedZoneName>
|
<CanonicalHostedZoneName>tests.us-east-1.elb.amazonaws.com</CanonicalHostedZoneName>
|
||||||
<CanonicalHostedZoneNameID>Z3ZONEID</CanonicalHostedZoneNameID>
|
<CanonicalHostedZoneNameID>Z3ZONEID</CanonicalHostedZoneNameID>
|
||||||
<Scheme>{{ load_balancer.scheme }}</Scheme>
|
<Scheme>{{ load_balancer.scheme }}</Scheme>
|
||||||
<DNSName>tests.us-east-1.elb.amazonaws.com</DNSName>
|
<DNSName>{{ load_balancer.dns_name }}</DNSName>
|
||||||
<BackendServerDescriptions>
|
<BackendServerDescriptions>
|
||||||
{% for backend in load_balancer.backends %}
|
{% for backend in load_balancer.backends %}
|
||||||
<member>
|
<member>
|
||||||
|
@ -85,6 +85,27 @@ class RecordSet(object):
|
|||||||
record_set = hosted_zone.add_rrset(properties)
|
record_set = hosted_zone.add_rrset(properties)
|
||||||
return record_set
|
return record_set
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
|
||||||
|
cls.delete_from_cloudformation_json(original_resource.name, cloudformation_json, region_name)
|
||||||
|
return cls.create_from_cloudformation_json(new_resource_name, cloudformation_json, region_name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
|
# this will break if you changed the zone the record is in, unfortunately
|
||||||
|
properties = cloudformation_json['Properties']
|
||||||
|
|
||||||
|
zone_name = properties.get("HostedZoneName")
|
||||||
|
if zone_name:
|
||||||
|
hosted_zone = route53_backend.get_hosted_zone_by_name(zone_name)
|
||||||
|
else:
|
||||||
|
hosted_zone = route53_backend.get_hosted_zone(properties["HostedZoneId"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
hosted_zone.delete_rrset_by_name(resource_name)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
def to_xml(self):
|
def to_xml(self):
|
||||||
template = Template("""<ResourceRecordSet>
|
template = Template("""<ResourceRecordSet>
|
||||||
<Name>{{ record_set.name }}</Name>
|
<Name>{{ record_set.name }}</Name>
|
||||||
|
@ -137,7 +137,7 @@ class Queue(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
|
||||||
properties = cloudformation_json['Properties']
|
properties = cloudformation_json['Properties']
|
||||||
queue_name = properties['QueueName']
|
queue_name = properties['QueueName']
|
||||||
|
|
||||||
|
@ -274,6 +274,7 @@ def test_update_stack():
|
|||||||
conn.update_stack("test_stack", dummy_template_json2)
|
conn.update_stack("test_stack", dummy_template_json2)
|
||||||
|
|
||||||
stack = conn.describe_stacks()[0]
|
stack = conn.describe_stacks()[0]
|
||||||
|
stack.stack_status.should.equal("UPDATE_COMPLETE")
|
||||||
stack.get_template().should.equal({
|
stack.get_template().should.equal({
|
||||||
'GetTemplateResponse': {
|
'GetTemplateResponse': {
|
||||||
'GetTemplateResult': {
|
'GetTemplateResult': {
|
||||||
@ -284,3 +285,25 @@ def test_update_stack():
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@mock_cloudformation
|
||||||
|
def test_update_stack():
|
||||||
|
conn = boto.connect_cloudformation()
|
||||||
|
conn.create_stack(
|
||||||
|
"test_stack",
|
||||||
|
template_body=dummy_template_json,
|
||||||
|
)
|
||||||
|
conn.update_stack("test_stack", use_previous_template=True)
|
||||||
|
|
||||||
|
stack = conn.describe_stacks()[0]
|
||||||
|
stack.stack_status.should.equal("UPDATE_COMPLETE")
|
||||||
|
stack.get_template().should.equal({
|
||||||
|
'GetTemplateResponse': {
|
||||||
|
'GetTemplateResult': {
|
||||||
|
'TemplateBody': dummy_template_json,
|
||||||
|
'ResponseMetadata': {
|
||||||
|
'RequestId': '2d06e36c-ac1d-11e0-a958-f9382b6eb86bEXAMPLE'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ -262,10 +262,17 @@ def test_stack_elb_integration_with_attached_ec2_instances():
|
|||||||
"Resources": {
|
"Resources": {
|
||||||
"MyELB": {
|
"MyELB": {
|
||||||
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
|
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
|
||||||
"Instances": [{"Ref": "Ec2Instance1"}],
|
|
||||||
"Properties": {
|
"Properties": {
|
||||||
|
"Instances": [{"Ref": "Ec2Instance1"}],
|
||||||
"LoadBalancerName": "test-elb",
|
"LoadBalancerName": "test-elb",
|
||||||
"AvailabilityZones": ['us-east1'],
|
"AvailabilityZones": ['us-east-1'],
|
||||||
|
"Listeners": [
|
||||||
|
{
|
||||||
|
"InstancePort": "80",
|
||||||
|
"LoadBalancerPort": "80",
|
||||||
|
"Protocol": "HTTP",
|
||||||
|
}
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Ec2Instance1": {
|
"Ec2Instance1": {
|
||||||
@ -293,7 +300,99 @@ def test_stack_elb_integration_with_attached_ec2_instances():
|
|||||||
ec2_instance = reservation.instances[0]
|
ec2_instance = reservation.instances[0]
|
||||||
|
|
||||||
load_balancer.instances[0].id.should.equal(ec2_instance.id)
|
load_balancer.instances[0].id.should.equal(ec2_instance.id)
|
||||||
list(load_balancer.availability_zones).should.equal(['us-east1'])
|
list(load_balancer.availability_zones).should.equal(['us-east-1'])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_elb()
|
||||||
|
@mock_cloudformation()
|
||||||
|
def test_stack_elb_integration_with_health_check():
|
||||||
|
elb_template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Resources": {
|
||||||
|
"MyELB": {
|
||||||
|
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
|
||||||
|
"Properties": {
|
||||||
|
"LoadBalancerName": "test-elb",
|
||||||
|
"AvailabilityZones": ['us-west-1'],
|
||||||
|
"HealthCheck": {
|
||||||
|
"HealthyThreshold": "3",
|
||||||
|
"Interval": "5",
|
||||||
|
"Target": "HTTP:80/healthcheck",
|
||||||
|
"Timeout": "4",
|
||||||
|
"UnhealthyThreshold": "2",
|
||||||
|
},
|
||||||
|
"Listeners": [
|
||||||
|
{
|
||||||
|
"InstancePort": "80",
|
||||||
|
"LoadBalancerPort": "80",
|
||||||
|
"Protocol": "HTTP",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
elb_template_json = json.dumps(elb_template)
|
||||||
|
|
||||||
|
conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||||
|
conn.create_stack(
|
||||||
|
"elb_stack",
|
||||||
|
template_body=elb_template_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
elb_conn = boto.ec2.elb.connect_to_region("us-west-1")
|
||||||
|
load_balancer = elb_conn.get_all_load_balancers()[0]
|
||||||
|
health_check = load_balancer.health_check
|
||||||
|
|
||||||
|
health_check.healthy_threshold.should.equal(3)
|
||||||
|
health_check.interval.should.equal(5)
|
||||||
|
health_check.target.should.equal("HTTP:80/healthcheck")
|
||||||
|
health_check.timeout.should.equal(4)
|
||||||
|
health_check.unhealthy_threshold.should.equal(2)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_elb()
|
||||||
|
@mock_cloudformation()
|
||||||
|
def test_stack_elb_integration_with_update():
|
||||||
|
elb_template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Resources": {
|
||||||
|
"MyELB": {
|
||||||
|
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
|
||||||
|
"Properties": {
|
||||||
|
"LoadBalancerName": "test-elb",
|
||||||
|
"AvailabilityZones": ['us-west-1a'],
|
||||||
|
"Listeners": [
|
||||||
|
{
|
||||||
|
"InstancePort": "80",
|
||||||
|
"LoadBalancerPort": "80",
|
||||||
|
"Protocol": "HTTP",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
elb_template_json = json.dumps(elb_template)
|
||||||
|
|
||||||
|
conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||||
|
conn.create_stack(
|
||||||
|
"elb_stack",
|
||||||
|
template_body=elb_template_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
elb_conn = boto.ec2.elb.connect_to_region("us-west-1")
|
||||||
|
load_balancer = elb_conn.get_all_load_balancers()[0]
|
||||||
|
load_balancer.availability_zones[0].should.equal('us-west-1a')
|
||||||
|
|
||||||
|
elb_template['Resources']['MyELB']['Properties']['AvailabilityZones'] = ['us-west-1b']
|
||||||
|
elb_template_json = json.dumps(elb_template)
|
||||||
|
conn.update_stack(
|
||||||
|
"elb_stack",
|
||||||
|
template_body=elb_template_json,
|
||||||
|
)
|
||||||
|
load_balancer = elb_conn.get_all_load_balancers()[0]
|
||||||
|
load_balancer.availability_zones[0].should.equal('us-west-1b')
|
||||||
|
|
||||||
|
|
||||||
@mock_ec2()
|
@mock_ec2()
|
||||||
@ -451,11 +550,11 @@ def test_autoscaling_group_with_elb():
|
|||||||
"Listeners": [{
|
"Listeners": [{
|
||||||
"LoadBalancerPort": "80",
|
"LoadBalancerPort": "80",
|
||||||
"InstancePort": "80",
|
"InstancePort": "80",
|
||||||
"Protocol": "HTTP"
|
"Protocol": "HTTP",
|
||||||
}],
|
}],
|
||||||
"LoadBalancerName": "my-elb",
|
"LoadBalancerName": "my-elb",
|
||||||
"HealthCheck": {
|
"HealthCheck": {
|
||||||
"Target": "80",
|
"Target": "HTTP:80",
|
||||||
"HealthyThreshold": "3",
|
"HealthyThreshold": "3",
|
||||||
"UnhealthyThreshold": "5",
|
"UnhealthyThreshold": "5",
|
||||||
"Interval": "30",
|
"Interval": "30",
|
||||||
@ -498,6 +597,55 @@ def test_autoscaling_group_with_elb():
|
|||||||
elb_resource.physical_resource_id.should.contain("my-elb")
|
elb_resource.physical_resource_id.should.contain("my-elb")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_autoscaling()
|
||||||
|
@mock_cloudformation()
|
||||||
|
def test_autoscaling_group_update():
|
||||||
|
asg_template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Resources": {
|
||||||
|
"my-as-group": {
|
||||||
|
"Type": "AWS::AutoScaling::AutoScalingGroup",
|
||||||
|
"Properties": {
|
||||||
|
"AvailabilityZones": ['us-west-1'],
|
||||||
|
"LaunchConfigurationName": {"Ref": "my-launch-config"},
|
||||||
|
"MinSize": "2",
|
||||||
|
"MaxSize": "2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"my-launch-config": {
|
||||||
|
"Type": "AWS::AutoScaling::LaunchConfiguration",
|
||||||
|
"Properties": {
|
||||||
|
"ImageId": "ami-1234abcd",
|
||||||
|
"UserData": "some user data",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
asg_template_json = json.dumps(asg_template)
|
||||||
|
|
||||||
|
conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||||
|
conn.create_stack(
|
||||||
|
"asg_stack",
|
||||||
|
template_body=asg_template_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
autoscale_conn = boto.ec2.autoscale.connect_to_region("us-west-1")
|
||||||
|
asg = autoscale_conn.get_all_groups()[0]
|
||||||
|
asg.min_size.should.equal(2)
|
||||||
|
asg.max_size.should.equal(2)
|
||||||
|
|
||||||
|
asg_template['Resources']['my-as-group']['Properties']['MaxSize'] = 3
|
||||||
|
asg_template_json = json.dumps(asg_template)
|
||||||
|
conn.update_stack(
|
||||||
|
"asg_stack",
|
||||||
|
template_body=asg_template_json,
|
||||||
|
)
|
||||||
|
asg = autoscale_conn.get_all_groups()[0]
|
||||||
|
asg.min_size.should.equal(2)
|
||||||
|
asg.max_size.should.equal(3)
|
||||||
|
|
||||||
|
|
||||||
@mock_ec2()
|
@mock_ec2()
|
||||||
@mock_cloudformation()
|
@mock_cloudformation()
|
||||||
def test_vpc_single_instance_in_subnet():
|
def test_vpc_single_instance_in_subnet():
|
||||||
@ -1072,6 +1220,46 @@ def test_route53_associate_health_check():
|
|||||||
record_set.health_check.should.equal(health_check_id)
|
record_set.health_check.should.equal(health_check_id)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cloudformation()
|
||||||
|
@mock_route53()
|
||||||
|
def test_route53_with_update():
|
||||||
|
route53_conn = boto.connect_route53()
|
||||||
|
|
||||||
|
template_json = json.dumps(route53_health_check.template)
|
||||||
|
cf_conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||||
|
cf_conn.create_stack(
|
||||||
|
"test_stack",
|
||||||
|
template_body=template_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
zones = route53_conn.get_all_hosted_zones()['ListHostedZonesResponse']['HostedZones']
|
||||||
|
list(zones).should.have.length_of(1)
|
||||||
|
zone_id = zones[0]['Id']
|
||||||
|
|
||||||
|
rrsets = route53_conn.get_all_rrsets(zone_id)
|
||||||
|
rrsets.should.have.length_of(1)
|
||||||
|
|
||||||
|
record_set = rrsets[0]
|
||||||
|
record_set.resource_records.should.equal(["my.example.com"])
|
||||||
|
|
||||||
|
route53_health_check.template['Resources']['myDNSRecord']['Properties']['ResourceRecords'] = ["my_other.example.com"]
|
||||||
|
template_json = json.dumps(route53_health_check.template)
|
||||||
|
cf_conn.update_stack(
|
||||||
|
"test_stack",
|
||||||
|
template_body=template_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
zones = route53_conn.get_all_hosted_zones()['ListHostedZonesResponse']['HostedZones']
|
||||||
|
list(zones).should.have.length_of(1)
|
||||||
|
zone_id = zones[0]['Id']
|
||||||
|
|
||||||
|
rrsets = route53_conn.get_all_rrsets(zone_id)
|
||||||
|
rrsets.should.have.length_of(1)
|
||||||
|
|
||||||
|
record_set = rrsets[0]
|
||||||
|
record_set.resource_records.should.equal(["my_other.example.com"])
|
||||||
|
|
||||||
|
|
||||||
@mock_cloudformation()
|
@mock_cloudformation()
|
||||||
@mock_sns()
|
@mock_sns()
|
||||||
def test_sns_topic():
|
def test_sns_topic():
|
||||||
@ -1382,6 +1570,51 @@ def test_security_group_ingress_separate_from_security_group_by_id_using_vpc():
|
|||||||
security_group1.rules[0].to_port.should.equal('8080')
|
security_group1.rules[0].to_port.should.equal('8080')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cloudformation
|
||||||
|
@mock_ec2
|
||||||
|
def test_security_group_with_update():
|
||||||
|
vpc_conn = boto.vpc.connect_to_region("us-west-1")
|
||||||
|
vpc1 = vpc_conn.create_vpc("10.0.0.0/16")
|
||||||
|
|
||||||
|
template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Resources": {
|
||||||
|
"test-security-group": {
|
||||||
|
"Type": "AWS::EC2::SecurityGroup",
|
||||||
|
"Properties": {
|
||||||
|
"GroupDescription": "test security group",
|
||||||
|
"VpcId": vpc1.id,
|
||||||
|
"Tags": [
|
||||||
|
{
|
||||||
|
"Key": "sg-name",
|
||||||
|
"Value": "sg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template_json = json.dumps(template)
|
||||||
|
cf_conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||||
|
cf_conn.create_stack(
|
||||||
|
"test_stack",
|
||||||
|
template_body=template_json,
|
||||||
|
)
|
||||||
|
security_group = vpc_conn.get_all_security_groups(filters={"tag:sg-name": "sg"})[0]
|
||||||
|
security_group.vpc_id.should.equal(vpc1.id)
|
||||||
|
|
||||||
|
vpc2 = vpc_conn.create_vpc("10.1.0.0/16")
|
||||||
|
template['Resources']['test-security-group']['Properties']['VpcId'] = vpc2.id
|
||||||
|
template_json = json.dumps(template)
|
||||||
|
cf_conn.update_stack(
|
||||||
|
"test_stack",
|
||||||
|
template_body=template_json,
|
||||||
|
)
|
||||||
|
security_group = vpc_conn.get_all_security_groups(filters={"tag:sg-name": "sg"})[0]
|
||||||
|
security_group.vpc_id.should.equal(vpc2.id)
|
||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
@mock_ec2
|
@mock_ec2
|
||||||
def test_subnets_should_be_created_with_availability_zone():
|
def test_subnets_should_be_created_with_availability_zone():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user