Merge branch 'master' into lotsa_stuff
This commit is contained in:
commit
c4273c9da1
11 changed files with 485 additions and 38 deletions
|
|
@ -298,7 +298,12 @@ class LambdaFunction(BaseModel):
|
|||
volumes=["{}:/var/task".format(data_vol.name)], environment=env_vars, detach=True, **run_kwargs)
|
||||
finally:
|
||||
if container:
|
||||
exit_code = container.wait()
|
||||
try:
|
||||
exit_code = container.wait(timeout=300)
|
||||
except requests.exceptions.ReadTimeout:
|
||||
exit_code = -1
|
||||
container.stop()
|
||||
container.kill()
|
||||
output = container.logs(stdout=False, stderr=True)
|
||||
output += container.logs(stdout=True, stderr=False)
|
||||
container.remove()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from moto.dynamodb import models as dynamodb_models
|
|||
from moto.ec2 import models as ec2_models
|
||||
from moto.ecs import models as ecs_models
|
||||
from moto.elb import models as elb_models
|
||||
from moto.elbv2 import models as elbv2_models
|
||||
from moto.iam import models as iam_models
|
||||
from moto.kinesis import models as kinesis_models
|
||||
from moto.kms import models as kms_models
|
||||
|
|
@ -61,6 +62,9 @@ MODEL_MAP = {
|
|||
"AWS::ECS::TaskDefinition": ecs_models.TaskDefinition,
|
||||
"AWS::ECS::Service": ecs_models.Service,
|
||||
"AWS::ElasticLoadBalancing::LoadBalancer": elb_models.FakeLoadBalancer,
|
||||
"AWS::ElasticLoadBalancingV2::LoadBalancer": elbv2_models.FakeLoadBalancer,
|
||||
"AWS::ElasticLoadBalancingV2::TargetGroup": elbv2_models.FakeTargetGroup,
|
||||
"AWS::ElasticLoadBalancingV2::Listener": elbv2_models.FakeListener,
|
||||
"AWS::DataPipeline::Pipeline": datapipeline_models.Pipeline,
|
||||
"AWS::IAM::InstanceProfile": iam_models.InstanceProfile,
|
||||
"AWS::IAM::Role": iam_models.Role,
|
||||
|
|
@ -326,7 +330,7 @@ def parse_output(output_logical_id, output_json, resources_map):
|
|||
output_json = clean_json(output_json, resources_map)
|
||||
output = Output()
|
||||
output.key = output_logical_id
|
||||
output.value = output_json['Value']
|
||||
output.value = clean_json(output_json['Value'], resources_map)
|
||||
output.description = output_json.get('Description')
|
||||
return output
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||
|
||||
import copy
|
||||
import itertools
|
||||
import ipaddress
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
|
@ -402,6 +403,10 @@ class Instance(TaggedEC2Resource, BotoInstance):
|
|||
subnet = ec2_backend.get_subnet(self.subnet_id)
|
||||
self.vpc_id = subnet.vpc_id
|
||||
self._placement.zone = subnet.availability_zone
|
||||
|
||||
if associate_public_ip is None:
|
||||
# Mapping public ip hasnt been explicitly enabled or disabled
|
||||
associate_public_ip = subnet.map_public_ip_on_launch == 'true'
|
||||
elif placement:
|
||||
self._placement.zone = placement
|
||||
else:
|
||||
|
|
@ -409,10 +414,22 @@ class Instance(TaggedEC2Resource, BotoInstance):
|
|||
|
||||
self.block_device_mapping = BlockDeviceMapping()
|
||||
|
||||
self.prep_nics(kwargs.get("nics", {}),
|
||||
subnet_id=self.subnet_id,
|
||||
private_ip=kwargs.get("private_ip"),
|
||||
associate_public_ip=associate_public_ip)
|
||||
self._private_ips = set()
|
||||
self.prep_nics(
|
||||
kwargs.get("nics", {}),
|
||||
private_ip=kwargs.get("private_ip"),
|
||||
associate_public_ip=associate_public_ip
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
subnet = self.ec2_backend.get_subnet(self.subnet_id)
|
||||
for ip in self._private_ips:
|
||||
subnet.del_subnet_ip(ip)
|
||||
except Exception:
|
||||
# Its not "super" critical we clean this up, as reset will do this
|
||||
# worst case we'll get IP address exaustion... rarely
|
||||
pass
|
||||
|
||||
def setup_defaults(self):
|
||||
# Default have an instance with root volume should you not wish to
|
||||
|
|
@ -547,14 +564,23 @@ class Instance(TaggedEC2Resource, BotoInstance):
|
|||
else:
|
||||
return self.security_groups
|
||||
|
||||
def prep_nics(self, nic_spec, subnet_id=None, private_ip=None, associate_public_ip=None):
|
||||
def prep_nics(self, nic_spec, private_ip=None, associate_public_ip=None):
|
||||
self.nics = {}
|
||||
|
||||
if not private_ip:
|
||||
if self.subnet_id:
|
||||
subnet = self.ec2_backend.get_subnet(self.subnet_id)
|
||||
if not private_ip:
|
||||
private_ip = subnet.get_available_subnet_ip(instance=self)
|
||||
else:
|
||||
subnet.request_ip(private_ip, instance=self)
|
||||
|
||||
self._private_ips.add(private_ip)
|
||||
elif private_ip is None:
|
||||
# Preserve old behaviour if in EC2-Classic mode
|
||||
private_ip = random_private_ip()
|
||||
|
||||
# Primary NIC defaults
|
||||
primary_nic = {'SubnetId': subnet_id,
|
||||
primary_nic = {'SubnetId': self.subnet_id,
|
||||
'PrivateIpAddress': private_ip,
|
||||
'AssociatePublicIpAddress': associate_public_ip}
|
||||
primary_nic = dict((k, v) for k, v in primary_nic.items() if v)
|
||||
|
|
@ -2114,10 +2140,17 @@ class Subnet(TaggedEC2Resource):
|
|||
self.id = subnet_id
|
||||
self.vpc_id = vpc_id
|
||||
self.cidr_block = cidr_block
|
||||
self.cidr = ipaddress.ip_network(six.text_type(self.cidr_block))
|
||||
self._availability_zone = availability_zone
|
||||
self.default_for_az = default_for_az
|
||||
self.map_public_ip_on_launch = map_public_ip_on_launch
|
||||
|
||||
# Theory is we assign ip's as we go (as 16,777,214 usable IPs in a /8)
|
||||
self._subnet_ip_generator = self.cidr.hosts()
|
||||
self.reserved_ips = [six.next(self._subnet_ip_generator) for _ in range(0, 3)] # Reserved by AWS
|
||||
self._unused_ips = set() # if instance is destroyed hold IP here for reuse
|
||||
self._subnet_ips = {} # has IP: instance
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
|
@ -2184,6 +2217,46 @@ class Subnet(TaggedEC2Resource):
|
|||
'"Fn::GetAtt" : [ "{0}" , "AvailabilityZone" ]"')
|
||||
raise UnformattedGetAttTemplateException()
|
||||
|
||||
def get_available_subnet_ip(self, instance):
|
||||
try:
|
||||
new_ip = self._unused_ips.pop()
|
||||
except KeyError:
|
||||
new_ip = six.next(self._subnet_ip_generator)
|
||||
|
||||
# Skips any IP's if they've been manually specified
|
||||
while str(new_ip) in self._subnet_ips:
|
||||
new_ip = six.next(self._subnet_ip_generator)
|
||||
|
||||
if new_ip == self.cidr.broadcast_address:
|
||||
raise StopIteration() # Broadcast address cant be used obviously
|
||||
# TODO StopIteration will be raised if no ip's available, not sure how aws handles this.
|
||||
|
||||
new_ip = str(new_ip)
|
||||
self._subnet_ips[new_ip] = instance
|
||||
|
||||
return new_ip
|
||||
|
||||
def request_ip(self, ip, instance):
|
||||
if ipaddress.ip_address(ip) not in self.cidr:
|
||||
raise Exception('IP does not fall in the subnet CIDR of {0}'.format(self.cidr))
|
||||
|
||||
if ip in self._subnet_ips:
|
||||
raise Exception('IP already in use')
|
||||
try:
|
||||
self._unused_ips.remove(ip)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
self._subnet_ips[ip] = instance
|
||||
return ip
|
||||
|
||||
def del_subnet_ip(self, ip):
|
||||
try:
|
||||
del self._subnet_ips[ip]
|
||||
self._unused_ips.add(ip)
|
||||
except KeyError:
|
||||
pass # Unknown IP
|
||||
|
||||
|
||||
class SubnetBackend(object):
|
||||
def __init__(self):
|
||||
|
|
|
|||
|
|
@ -57,7 +57,8 @@ class FakeTargetGroup(BaseModel):
|
|||
healthcheck_timeout_seconds,
|
||||
healthy_threshold_count,
|
||||
unhealthy_threshold_count,
|
||||
http_codes):
|
||||
matcher=None,
|
||||
target_type=None):
|
||||
self.name = name
|
||||
self.arn = arn
|
||||
self.vpc_id = vpc_id
|
||||
|
|
@ -72,7 +73,8 @@ class FakeTargetGroup(BaseModel):
|
|||
self.unhealthy_threshold_count = unhealthy_threshold_count
|
||||
self.load_balancer_arns = []
|
||||
self.tags = {}
|
||||
self.http_status_codes = http_codes
|
||||
self.matcher = matcher
|
||||
self.target_type = target_type
|
||||
|
||||
self.attributes = {
|
||||
'deregistration_delay.timeout_seconds': 300,
|
||||
|
|
@ -81,6 +83,10 @@ class FakeTargetGroup(BaseModel):
|
|||
|
||||
self.targets = OrderedDict()
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.arn
|
||||
|
||||
def register(self, targets):
|
||||
for target in targets:
|
||||
self.targets[target['id']] = {
|
||||
|
|
@ -105,6 +111,46 @@ class FakeTargetGroup(BaseModel):
|
|||
raise InvalidTargetError()
|
||||
return FakeHealthStatus(t['id'], t['port'], self.healthcheck_port, 'healthy')
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
elbv2_backend = elbv2_backends[region_name]
|
||||
|
||||
# per cloudformation docs:
|
||||
# The target group name should be shorter than 22 characters because
|
||||
# AWS CloudFormation uses the target group name to create the name of the load balancer.
|
||||
name = properties.get('Name', resource_name[:22])
|
||||
vpc_id = properties.get("VpcId")
|
||||
protocol = properties.get('Protocol')
|
||||
port = properties.get("Port")
|
||||
healthcheck_protocol = properties.get("HealthCheckProtocol")
|
||||
healthcheck_port = properties.get("HealthCheckPort")
|
||||
healthcheck_path = properties.get("HealthCheckPath")
|
||||
healthcheck_interval_seconds = properties.get("HealthCheckIntervalSeconds")
|
||||
healthcheck_timeout_seconds = properties.get("HealthCheckTimeoutSeconds")
|
||||
healthy_threshold_count = properties.get("HealthyThresholdCount")
|
||||
unhealthy_threshold_count = properties.get("UnhealthyThresholdCount")
|
||||
matcher = properties.get("Matcher")
|
||||
target_type = properties.get("TargetType")
|
||||
|
||||
target_group = elbv2_backend.create_target_group(
|
||||
name=name,
|
||||
vpc_id=vpc_id,
|
||||
protocol=protocol,
|
||||
port=port,
|
||||
healthcheck_protocol=healthcheck_protocol,
|
||||
healthcheck_port=healthcheck_port,
|
||||
healthcheck_path=healthcheck_path,
|
||||
healthcheck_interval_seconds=healthcheck_interval_seconds,
|
||||
healthcheck_timeout_seconds=healthcheck_timeout_seconds,
|
||||
healthy_threshold_count=healthy_threshold_count,
|
||||
unhealthy_threshold_count=unhealthy_threshold_count,
|
||||
matcher=matcher,
|
||||
target_type=target_type,
|
||||
)
|
||||
return target_group
|
||||
|
||||
|
||||
class FakeListener(BaseModel):
|
||||
|
||||
|
|
@ -126,6 +172,10 @@ class FakeListener(BaseModel):
|
|||
is_default=True
|
||||
)
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.arn
|
||||
|
||||
@property
|
||||
def rules(self):
|
||||
return self._non_default_rules + [self._default_rule]
|
||||
|
|
@ -137,6 +187,28 @@ class FakeListener(BaseModel):
|
|||
self._non_default_rules.append(rule)
|
||||
self._non_default_rules = sorted(self._non_default_rules, key=lambda x: x.priority)
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
elbv2_backend = elbv2_backends[region_name]
|
||||
load_balancer_arn = properties.get("LoadBalancerArn")
|
||||
protocol = properties.get("Protocol")
|
||||
port = properties.get("Port")
|
||||
ssl_policy = properties.get("SslPolicy")
|
||||
certificates = properties.get("Certificates")
|
||||
# transform default actions to confirm with the rest of the code and XML templates
|
||||
if "DefaultActions" in properties:
|
||||
default_actions = []
|
||||
for action in properties['DefaultActions']:
|
||||
default_actions.append({'type': action['Type'], 'target_group_arn': action['TargetGroupArn']})
|
||||
else:
|
||||
default_actions = None
|
||||
|
||||
listener = elbv2_backend.create_listener(
|
||||
load_balancer_arn, protocol, port, ssl_policy, certificates, default_actions)
|
||||
return listener
|
||||
|
||||
|
||||
class FakeRule(BaseModel):
|
||||
|
||||
|
|
@ -186,7 +258,7 @@ class FakeLoadBalancer(BaseModel):
|
|||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.name
|
||||
return self.arn
|
||||
|
||||
def add_tag(self, key, value):
|
||||
if len(self.tags) >= 10 and key not in self.tags:
|
||||
|
|
@ -204,6 +276,27 @@ class FakeLoadBalancer(BaseModel):
|
|||
''' Not exposed as part of the ELB API - used for CloudFormation. '''
|
||||
elbv2_backends[region].delete_load_balancer(self.arn)
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
elbv2_backend = elbv2_backends[region_name]
|
||||
|
||||
name = properties.get('Name', resource_name)
|
||||
security_groups = properties.get("SecurityGroups")
|
||||
subnet_ids = properties.get('Subnets')
|
||||
scheme = properties.get('Scheme', 'internet-facing')
|
||||
|
||||
load_balancer = elbv2_backend.create_load_balancer(name, security_groups, subnet_ids, scheme=scheme)
|
||||
return load_balancer
|
||||
|
||||
def get_cfn_attribute(self, attribute_name):
|
||||
attributes = {
|
||||
'DNSName': self.dns_name,
|
||||
'LoadBalancerName': self.name,
|
||||
}
|
||||
return attributes[attribute_name]
|
||||
|
||||
|
||||
class ELBv2Backend(BaseBackend):
|
||||
|
||||
|
|
@ -316,7 +409,7 @@ class ELBv2Backend(BaseBackend):
|
|||
def create_target_group(self, name, **kwargs):
|
||||
if len(name) > 32:
|
||||
raise InvalidTargetGroupNameError(
|
||||
"Target group name '%s' cannot be longer than '32' characters" % name
|
||||
"Target group name '%s' cannot be longer than '22' characters" % name
|
||||
)
|
||||
if not re.match('^[a-zA-Z0-9\-]+$', name):
|
||||
raise InvalidTargetGroupNameError(
|
||||
|
|
|
|||
|
|
@ -734,9 +734,14 @@ CREATE_TARGET_GROUP_TEMPLATE = """<CreateTargetGroupResponse xmlns="http://elast
|
|||
<HealthCheckTimeoutSeconds>{{ target_group.healthcheck_timeout_seconds }}</HealthCheckTimeoutSeconds>
|
||||
<HealthyThresholdCount>{{ target_group.healthy_threshold_count }}</HealthyThresholdCount>
|
||||
<UnhealthyThresholdCount>{{ target_group.unhealthy_threshold_count }}</UnhealthyThresholdCount>
|
||||
{% if target_group.matcher %}
|
||||
<Matcher>
|
||||
<HttpCode>200</HttpCode>
|
||||
<HttpCode>{{ target_group.matcher['HttpCode'] }}</HttpCode>
|
||||
</Matcher>
|
||||
{% endif %}
|
||||
{% if target_group.target_type %}
|
||||
<TargetType>{{ target_group.target_type }}</TargetType>
|
||||
{% endif %}
|
||||
</member>
|
||||
</TargetGroups>
|
||||
</CreateTargetGroupResult>
|
||||
|
|
@ -836,6 +841,7 @@ DESCRIBE_LOAD_BALANCERS_TEMPLATE = """<DescribeLoadBalancersResponse xmlns="http
|
|||
<Code>provisioning</Code>
|
||||
</State>
|
||||
<Type>application</Type>
|
||||
<IpAddressType>ipv4</IpAddressType>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</LoadBalancers>
|
||||
|
|
@ -905,9 +911,14 @@ DESCRIBE_TARGET_GROUPS_TEMPLATE = """<DescribeTargetGroupsResponse xmlns="http:/
|
|||
<HealthCheckTimeoutSeconds>{{ target_group.healthcheck_timeout_seconds }}</HealthCheckTimeoutSeconds>
|
||||
<HealthyThresholdCount>{{ target_group.healthy_threshold_count }}</HealthyThresholdCount>
|
||||
<UnhealthyThresholdCount>{{ target_group.unhealthy_threshold_count }}</UnhealthyThresholdCount>
|
||||
{% if target_group.matcher %}
|
||||
<Matcher>
|
||||
<HttpCode>{{ target_group.http_status_codes }}</HttpCode>
|
||||
<HttpCode>{{ target_group.matcher['HttpCode'] }}</HttpCode>
|
||||
</Matcher>
|
||||
{% endif %}
|
||||
{% if target_group.target_type %}
|
||||
<TargetType>{{ target_group.target_type }}</TargetType>
|
||||
{% endif %}
|
||||
<LoadBalancerArns>
|
||||
{% for load_balancer_arn in target_group.load_balancer_arns %}
|
||||
<member>{{ load_balancer_arn }}</member>
|
||||
|
|
@ -1351,7 +1362,7 @@ MODIFY_TARGET_GROUP_TEMPLATE = """<ModifyTargetGroupResponse xmlns="http://elast
|
|||
<HealthyThresholdCount>{{ target_group.healthy_threshold_count }}</HealthyThresholdCount>
|
||||
<UnhealthyThresholdCount>{{ target_group.unhealthy_threshold_count }}</UnhealthyThresholdCount>
|
||||
<Matcher>
|
||||
<HttpCode>{{ target_group.http_status_codes }}</HttpCode>
|
||||
<HttpCode>{{ target_group.matcher['HttpCode'] }}</HttpCode>
|
||||
</Matcher>
|
||||
<LoadBalancerArns>
|
||||
{% for load_balancer_arn in target_group.load_balancer_arns %}
|
||||
|
|
|
|||
|
|
@ -704,7 +704,8 @@ class RDS2Backend(BaseBackend):
|
|||
if self.arn_regex.match(source_database_id):
|
||||
db_kwargs['region'] = self.region
|
||||
|
||||
replica = copy.deepcopy(primary)
|
||||
# Shouldn't really copy here as the instance is duplicated. RDS replicas have different instances.
|
||||
replica = copy.copy(primary)
|
||||
replica.update(db_kwargs)
|
||||
replica.set_as_replica()
|
||||
self.databases[database_id] = replica
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue