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.ec2 import ec2_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
|
||||
DEFAULT_COOLDOWN = 300
|
||||
@ -80,6 +81,19 @@ class FakeLaunchConfiguration(object):
|
||||
)
|
||||
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):
|
||||
backend = autoscaling_backends[region_name]
|
||||
backend.delete_launch_configuration(self.name)
|
||||
@ -171,9 +185,29 @@ class FakeAutoScalingGroup(object):
|
||||
)
|
||||
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):
|
||||
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
|
||||
def physical_resource_id(self):
|
||||
|
@ -14,7 +14,7 @@ class ValidationError(BadRequest):
|
||||
super(ValidationError, self).__init__()
|
||||
self.description = template.render(
|
||||
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__()
|
||||
self.description = template.render(
|
||||
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.resource_map.update(json.loads(template))
|
||||
self.output_map = self._create_output_map()
|
||||
self.status = 'UPDATE_COMPLETE'
|
||||
self.status = "UPDATE_COMPLETE"
|
||||
|
||||
def delete(self):
|
||||
self.resource_map.delete()
|
||||
self.status = 'DELETE_COMPLETE'
|
||||
self.status = "DELETE_COMPLETE"
|
||||
|
||||
|
||||
class CloudFormationBackend(BaseBackend):
|
||||
|
@ -115,7 +115,7 @@ def clean_json(resource_json, resources_map):
|
||||
return result
|
||||
|
||||
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:
|
||||
return resource_json
|
||||
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):
|
||||
resource_class, resource_json, resource_name = parse_resource(logical_id, resource_json, resources_map)
|
||||
resource = resource_class.update_from_cloudformation_json(resource_name, resource_json, region_name)
|
||||
return resource
|
||||
resource_class, new_resource_json, new_resource_name = parse_resource(logical_id, resource_json, resources_map)
|
||||
original_resource = resources_map[logical_id]
|
||||
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):
|
||||
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)
|
||||
return None
|
||||
|
||||
|
||||
def parse_condition(condition, resources_map, condition_map):
|
||||
@ -289,6 +296,8 @@ class ResourceMap(collections.Mapping):
|
||||
return self._parsed_resources[resource_logical_id]
|
||||
else:
|
||||
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)
|
||||
self._parsed_resources[resource_logical_id] = 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)
|
||||
self._parsed_resources.pop(resource_name)
|
||||
|
||||
for resource_name in new_template:
|
||||
if resource_name in old_template and new_template[resource_name] != old_template[resource_name]:
|
||||
resources_to_update = set(name for name in new_template if name in old_template and new_template[name] != old_template[name])
|
||||
tries = 1
|
||||
while resources_to_update and tries < 5:
|
||||
for resource_name in resources_to_update.copy():
|
||||
resource_json = new_template[resource_name]
|
||||
|
||||
changed_resource = parse_and_update_resource(resource_name, resource_json, self, self._region_name)
|
||||
self._parsed_resources[resource_name] = changed_resource
|
||||
try:
|
||||
changed_resource = parse_and_update_resource(resource_name, resource_json, self, self._region_name)
|
||||
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):
|
||||
for resource in self.resources:
|
||||
parsed_resource = self._parsed_resources.pop(resource)
|
||||
if parsed_resource and hasattr(parsed_resource, 'delete'):
|
||||
parsed_resource.delete(self._region_name)
|
||||
remaining_resources = set(self.resources)
|
||||
tries = 1
|
||||
while remaining_resources and tries < 5:
|
||||
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):
|
||||
|
@ -79,8 +79,8 @@ class CloudFormationResponse(BaseResponse):
|
||||
resource = stack_resource
|
||||
break
|
||||
else:
|
||||
raise ValidationError(logical_resource_id)
|
||||
|
||||
raise ValidationError(logical_resource_id)
|
||||
|
||||
template = self.response_template(DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE)
|
||||
return template.render(stack=stack, resource=resource)
|
||||
|
||||
@ -106,7 +106,7 @@ class CloudFormationResponse(BaseResponse):
|
||||
def get_template(self):
|
||||
name_or_stack_id = self.querystring.get('StackName')[0]
|
||||
stack = self.cloudformation_backend.get_stack(name_or_stack_id)
|
||||
|
||||
|
||||
if self.request_json:
|
||||
return json.dumps({
|
||||
"GetTemplateResponse": {
|
||||
@ -124,21 +124,24 @@ class CloudFormationResponse(BaseResponse):
|
||||
|
||||
def update_stack(self):
|
||||
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(
|
||||
name=stack_name,
|
||||
template=stack_body,
|
||||
)
|
||||
|
||||
if self.request_json:
|
||||
return json.dumps({
|
||||
stack_body = {
|
||||
'UpdateStackResponse': {
|
||||
'UpdateStackResult': {
|
||||
'StackId': stack.name,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return json.dumps(stack_body)
|
||||
else:
|
||||
template = self.response_template(UPDATE_STACK_RESPONSE_TEMPLATE)
|
||||
return template.render(stack=stack)
|
||||
@ -310,6 +313,16 @@ GET_TEMPLATE_RESPONSE_TEMPLATE = """<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>
|
||||
<ResponseMetadata>
|
||||
<RequestId>5ccc7dcd-744c-11e5-be70-example</RequestId>
|
||||
|
@ -434,6 +434,9 @@ class Instance(BotoInstance, TaggedEC2Resource):
|
||||
self._state_reason = StateReason("Client.UserInitiatedShutdown: User initiated shutdown",
|
||||
"Client.UserInitiatedShutdown")
|
||||
|
||||
def delete(self, region):
|
||||
self.terminate()
|
||||
|
||||
def terminate(self, *args, **kwargs):
|
||||
for nic in self.nics.values():
|
||||
nic.stop()
|
||||
@ -1135,6 +1138,29 @@ class SecurityGroup(TaggedEC2Resource):
|
||||
|
||||
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
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
|
@ -64,20 +64,21 @@ class FakeLoadBalancer(object):
|
||||
self.subnets = subnets or []
|
||||
self.vpc_id = vpc_id or 'vpc-56e10e3d'
|
||||
self.tags = {}
|
||||
self.dns_name = "tests.us-east-1.elb.amazonaws.com"
|
||||
|
||||
for port in ports:
|
||||
listener = FakeListener(
|
||||
protocol=port['protocol'],
|
||||
load_balancer_port=port['load_balancer_port'],
|
||||
instance_port=port['instance_port'],
|
||||
ssl_certificate_id=port.get('sslcertificate_id'),
|
||||
protocol=(port.get('protocol') or port['Protocol']),
|
||||
load_balancer_port=(port.get('load_balancer_port') or port['LoadBalancerPort']),
|
||||
instance_port=(port.get('instance_port') or port['InstancePort']),
|
||||
ssl_certificate_id=port.get('sslcertificate_id', port.get('SSLCertificateId')),
|
||||
)
|
||||
self.listeners.append(listener)
|
||||
|
||||
# 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*
|
||||
backend = FakeBackend(
|
||||
instance_port=port['instance_port'],
|
||||
instance_port=(port.get('instance_port') or port['InstancePort']),
|
||||
)
|
||||
self.backends.append(backend)
|
||||
|
||||
@ -88,15 +89,41 @@ class FakeLoadBalancer(object):
|
||||
elb_backend = elb_backends[region_name]
|
||||
new_elb = elb_backend.create_load_balancer(
|
||||
name=properties.get('LoadBalancerName', resource_name),
|
||||
zones=properties.get('AvailabilityZones'),
|
||||
ports=[],
|
||||
zones=properties.get('AvailabilityZones', []),
|
||||
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:
|
||||
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
|
||||
|
||||
@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
|
||||
def physical_resource_id(self):
|
||||
return self.name
|
||||
@ -108,7 +135,7 @@ class FakeLoadBalancer(object):
|
||||
elif attribute_name == 'CanonicalHostedZoneNameID':
|
||||
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "CanonicalHostedZoneNameID" ]"')
|
||||
elif attribute_name == 'DNSName':
|
||||
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "DNSName" ]"')
|
||||
return self.dns_name
|
||||
elif attribute_name == 'SourceSecurityGroup.GroupName':
|
||||
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "SourceSecurityGroup.GroupName" ]"')
|
||||
elif attribute_name == 'SourceSecurityGroup.OwnerAlias':
|
||||
@ -149,6 +176,10 @@ class FakeLoadBalancer(object):
|
||||
if key in self.tags:
|
||||
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):
|
||||
|
||||
|
@ -12,8 +12,7 @@ from boto.ec2.elb.policies import (
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import elb_backends
|
||||
from .exceptions import DuplicateTagKeysError, LoadBalancerNotFoundError, \
|
||||
TooManyTagsError
|
||||
from .exceptions import DuplicateTagKeysError, LoadBalancerNotFoundError
|
||||
|
||||
|
||||
class ELBResponse(BaseResponse):
|
||||
@ -29,16 +28,16 @@ class ELBResponse(BaseResponse):
|
||||
scheme = self._get_param('Scheme')
|
||||
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,
|
||||
zones=availability_zones,
|
||||
ports=ports,
|
||||
scheme=scheme,
|
||||
subnets=subnets,
|
||||
)
|
||||
self._add_tags(elb)
|
||||
self._add_tags(load_balancer)
|
||||
template = self.response_template(CREATE_LOAD_BALANCER_TEMPLATE)
|
||||
return template.render()
|
||||
return template.render(load_balancer=load_balancer)
|
||||
|
||||
def create_load_balancer_listeners(self):
|
||||
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))
|
||||
elb = self.elb_backend.get_load_balancer(load_balancer_name)
|
||||
if not elb:
|
||||
raise LoadBalancerNotFound(load_balancer_name)
|
||||
raise LoadBalancerNotFoundError(load_balancer_name)
|
||||
|
||||
key = 'Tag.member.{0}.Key'.format(number)
|
||||
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))
|
||||
elb = self.elb_backend.get_load_balancer(load_balancer_name)
|
||||
if not elb:
|
||||
raise LoadBalancerNotFound(load_balancer_name)
|
||||
raise LoadBalancerNotFoundError(load_balancer_name)
|
||||
elbs.append(elb)
|
||||
|
||||
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/">
|
||||
<CreateLoadBalancerResult>
|
||||
<DNSName>tests.us-east-1.elb.amazonaws.com</DNSName>
|
||||
<DNSName>{{ load_balancer.dns_name }}</DNSName>
|
||||
</CreateLoadBalancerResult>
|
||||
<ResponseMetadata>
|
||||
<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>
|
||||
<CanonicalHostedZoneNameID>Z3ZONEID</CanonicalHostedZoneNameID>
|
||||
<Scheme>{{ load_balancer.scheme }}</Scheme>
|
||||
<DNSName>tests.us-east-1.elb.amazonaws.com</DNSName>
|
||||
<DNSName>{{ load_balancer.dns_name }}</DNSName>
|
||||
<BackendServerDescriptions>
|
||||
{% for backend in load_balancer.backends %}
|
||||
<member>
|
||||
|
@ -85,6 +85,27 @@ class RecordSet(object):
|
||||
record_set = hosted_zone.add_rrset(properties)
|
||||
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):
|
||||
template = Template("""<ResourceRecordSet>
|
||||
<Name>{{ record_set.name }}</Name>
|
||||
|
@ -137,7 +137,7 @@ class Queue(object):
|
||||
)
|
||||
|
||||
@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']
|
||||
queue_name = properties['QueueName']
|
||||
|
||||
|
@ -274,6 +274,7 @@ def test_update_stack():
|
||||
conn.update_stack("test_stack", dummy_template_json2)
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
stack.stack_status.should.equal("UPDATE_COMPLETE")
|
||||
stack.get_template().should.equal({
|
||||
'GetTemplateResponse': {
|
||||
'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": {
|
||||
"MyELB": {
|
||||
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
|
||||
"Instances": [{"Ref": "Ec2Instance1"}],
|
||||
"Properties": {
|
||||
"Instances": [{"Ref": "Ec2Instance1"}],
|
||||
"LoadBalancerName": "test-elb",
|
||||
"AvailabilityZones": ['us-east1'],
|
||||
"AvailabilityZones": ['us-east-1'],
|
||||
"Listeners": [
|
||||
{
|
||||
"InstancePort": "80",
|
||||
"LoadBalancerPort": "80",
|
||||
"Protocol": "HTTP",
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
"Ec2Instance1": {
|
||||
@ -293,7 +300,99 @@ def test_stack_elb_integration_with_attached_ec2_instances():
|
||||
ec2_instance = reservation.instances[0]
|
||||
|
||||
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()
|
||||
@ -451,11 +550,11 @@ def test_autoscaling_group_with_elb():
|
||||
"Listeners": [{
|
||||
"LoadBalancerPort": "80",
|
||||
"InstancePort": "80",
|
||||
"Protocol": "HTTP"
|
||||
"Protocol": "HTTP",
|
||||
}],
|
||||
"LoadBalancerName": "my-elb",
|
||||
"HealthCheck": {
|
||||
"Target": "80",
|
||||
"Target": "HTTP:80",
|
||||
"HealthyThreshold": "3",
|
||||
"UnhealthyThreshold": "5",
|
||||
"Interval": "30",
|
||||
@ -498,6 +597,55 @@ def test_autoscaling_group_with_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_cloudformation()
|
||||
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)
|
||||
|
||||
|
||||
@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_sns()
|
||||
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')
|
||||
|
||||
|
||||
@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_ec2
|
||||
def test_subnets_should_be_created_with_availability_zone():
|
||||
|
Loading…
Reference in New Issue
Block a user