Merge pull request #33 from spulec/master

Merge upstream
This commit is contained in:
Bert Blommers 2020-03-09 12:19:12 +00:00 committed by GitHub
commit 1aa99bb405
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 461 additions and 75 deletions

View File

@ -981,7 +981,7 @@ class LambdaBackend(BaseBackend):
]
}
func = self._lambdas.get_arn(function_arn)
func.invoke(json.dumps(event), {}, {})
return func.invoke(json.dumps(event), {}, {})
def list_tags(self, resource):
return self.get_function_by_arn(resource).tags

View File

@ -366,3 +366,13 @@ class TooManyResourceKeys(JsonRESTError):
message = str(message)
super(TooManyResourceKeys, self).__init__("ValidationException", message)
class InvalidResultTokenException(JsonRESTError):
code = 400
def __init__(self):
message = "The resultToken provided is invalid"
super(InvalidResultTokenException, self).__init__(
"InvalidResultTokenException", message
)

View File

@ -40,6 +40,7 @@ from moto.config.exceptions import (
TooManyResourceIds,
ResourceNotDiscoveredException,
TooManyResourceKeys,
InvalidResultTokenException,
)
from moto.core import BaseBackend, BaseModel
@ -1089,6 +1090,26 @@ class ConfigBackend(BaseBackend):
"UnprocessedResourceIdentifiers": not_found,
}
def put_evaluations(self, evaluations=None, result_token=None, test_mode=False):
if not evaluations:
raise InvalidParameterValueException(
"The Evaluations object in your request cannot be null."
"Add the required parameters and try again."
)
if not result_token:
raise InvalidResultTokenException()
# Moto only supports PutEvaluations with test mode currently (missing rule and token support)
if not test_mode:
raise NotImplementedError(
"PutEvaluations without TestMode is not yet implemented"
)
return {
"FailedEvaluations": [],
} # At this time, moto is not adding failed evaluations.
config_backends = {}
for region in Session().get_available_regions("config"):

View File

@ -151,3 +151,11 @@ class ConfigResponse(BaseResponse):
self._get_param("ResourceIdentifiers"),
)
return json.dumps(schema)
def put_evaluations(self):
evaluations = self.config_backend.put_evaluations(
self._get_param("Evaluations"),
self._get_param("ResultToken"),
self._get_param("TestMode"),
)
return json.dumps(evaluations)

View File

@ -7,6 +7,7 @@ import inspect
import os
import re
import six
import types
from io import BytesIO
from collections import defaultdict
from botocore.handlers import BUILTIN_HANDLERS
@ -217,12 +218,29 @@ botocore_mock = responses.RequestsMock(
assert_all_requests_are_fired=False,
target="botocore.vendored.requests.adapters.HTTPAdapter.send",
)
responses_mock = responses._default_mock
# Add passthrough to allow any other requests to work
# Since this uses .startswith, it applies to http and https requests.
responses_mock.add_passthru("http")
def _find_first_match(self, request):
for i, match in enumerate(self._matches):
if match.matches(request):
return match
return None
# Modify behaviour of the matcher to only/always return the first match
# Default behaviour is to return subsequent matches for subsequent requests, which leads to https://github.com/spulec/moto/issues/2567
# - First request matches on the appropriate S3 URL
# - Same request, executed again, will be matched on the subsequent match, which happens to be the catch-all, not-yet-implemented, callback
# Fix: Always return the first match
responses_mock._find_match = types.MethodType(_find_first_match, responses_mock)
BOTOCORE_HTTP_METHODS = ["GET", "DELETE", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]

View File

@ -822,6 +822,21 @@ class Instance(TaggedEC2Resource, BotoInstance):
return self.public_ip
raise UnformattedGetAttTemplateException()
def applies(self, filters):
if filters:
applicable = False
for f in filters:
acceptable_values = f["values"]
if f["name"] == "instance-state-name":
if self._state.name in acceptable_values:
applicable = True
if f["name"] == "instance-state-code":
if str(self._state.code) in acceptable_values:
applicable = True
return applicable
# If there are no filters, all instances are valid
return True
class InstanceBackend(object):
def __init__(self):
@ -921,22 +936,23 @@ class InstanceBackend(object):
value = getattr(instance, key)
return instance, value
def all_instances(self):
def all_instances(self, filters=None):
instances = []
for reservation in self.all_reservations():
for instance in reservation.instances:
instances.append(instance)
return instances
def all_running_instances(self):
instances = []
for reservation in self.all_reservations():
for instance in reservation.instances:
if instance.state_code == 16:
if instance.applies(filters):
instances.append(instance)
return instances
def get_multi_instances_by_id(self, instance_ids):
def all_running_instances(self, filters=None):
instances = []
for reservation in self.all_reservations():
for instance in reservation.instances:
if instance.state_code == 16 and instance.applies(filters):
instances.append(instance)
return instances
def get_multi_instances_by_id(self, instance_ids, filters=None):
"""
:param instance_ids: A string list with instance ids
:return: A list with instance objects
@ -946,7 +962,8 @@ class InstanceBackend(object):
for reservation in self.all_reservations():
for instance in reservation.instances:
if instance.id in instance_ids:
result.append(instance)
if instance.applies(filters):
result.append(instance)
# TODO: Trim error message down to specific invalid id.
if instance_ids and len(instance_ids) > len(result):
@ -1722,6 +1739,12 @@ class SecurityGroup(TaggedEC2Resource):
self.vpc_id = vpc_id
self.owner_id = OWNER_ID
# Append default IPv6 egress rule for VPCs with IPv6 support
if vpc_id:
vpc = self.ec2_backend.vpcs.get(vpc_id)
if vpc and len(vpc.get_cidr_block_association_set(ipv6=True)) > 0:
self.egress_rules.append(SecurityRule("-1", None, None, [], []))
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -113,16 +113,34 @@ class InstanceResponse(BaseResponse):
template = self.response_template(EC2_START_INSTANCES)
return template.render(instances=instances)
def _get_list_of_dict_params(self, param_prefix, _dct):
"""
Simplified version of _get_dict_param
Allows you to pass in a custom dict instead of using self.querystring by default
"""
params = []
for key, value in _dct.items():
if key.startswith(param_prefix):
params.append(value)
return params
def describe_instance_status(self):
instance_ids = self._get_multi_param("InstanceId")
include_all_instances = self._get_param("IncludeAllInstances") == "true"
filters = self._get_list_prefix("Filter")
filters = [
{"name": f["name"], "values": self._get_list_of_dict_params("value.", f)}
for f in filters
]
if instance_ids:
instances = self.ec2_backend.get_multi_instances_by_id(instance_ids)
instances = self.ec2_backend.get_multi_instances_by_id(
instance_ids, filters
)
elif include_all_instances:
instances = self.ec2_backend.all_instances()
instances = self.ec2_backend.all_instances(filters)
else:
instances = self.ec2_backend.all_running_instances()
instances = self.ec2_backend.all_running_instances(filters)
template = self.response_template(EC2_INSTANCE_STATUS)
return template.render(instances=instances)

View File

@ -1,5 +1,5 @@
from __future__ import unicode_literals
from moto.core.exceptions import RESTError
from moto.core.exceptions import RESTError, JsonRESTError
class RepositoryNotFoundException(RESTError):
@ -13,7 +13,7 @@ class RepositoryNotFoundException(RESTError):
)
class ImageNotFoundException(RESTError):
class ImageNotFoundException(JsonRESTError):
code = 400
def __init__(self, image_id, repository_name, registry_id):

View File

@ -35,6 +35,7 @@ class FakeInstanceGroup(BaseModel):
name=None,
id=None,
bid_price=None,
ebs_configuration=None,
):
self.id = id or random_instance_group_id()
@ -51,6 +52,7 @@ class FakeInstanceGroup(BaseModel):
self.num_instances = instance_count
self.role = instance_role
self.type = instance_type
self.ebs_configuration = ebs_configuration
self.creation_datetime = datetime.now(pytz.utc)
self.start_datetime = datetime.now(pytz.utc)

View File

@ -73,6 +73,8 @@ class ElasticMapReduceResponse(BaseResponse):
instance_groups = self._get_list_prefix("InstanceGroups.member")
for item in instance_groups:
item["instance_count"] = int(item["instance_count"])
# Adding support to EbsConfiguration
self._parse_ebs_configuration(item)
instance_groups = self.backend.add_instance_groups(jobflow_id, instance_groups)
template = self.response_template(ADD_INSTANCE_GROUPS_TEMPLATE)
return template.render(instance_groups=instance_groups)
@ -324,6 +326,8 @@ class ElasticMapReduceResponse(BaseResponse):
if instance_groups:
for ig in instance_groups:
ig["instance_count"] = int(ig["instance_count"])
# Adding support to EbsConfiguration
self._parse_ebs_configuration(ig)
self.backend.add_instance_groups(cluster.id, instance_groups)
tags = self._get_list_prefix("Tags.member")
@ -335,6 +339,85 @@ class ElasticMapReduceResponse(BaseResponse):
template = self.response_template(RUN_JOB_FLOW_TEMPLATE)
return template.render(cluster=cluster)
def _has_key_prefix(self, key_prefix, value):
for key in value: # iter on both keys and values
if key.startswith(key_prefix):
return True
return False
def _parse_ebs_configuration(self, instance_group):
key_ebs_config = "ebs_configuration"
ebs_configuration = dict()
# Filter only EBS config keys
for key in instance_group:
if key.startswith(key_ebs_config):
ebs_configuration[key] = instance_group[key]
if len(ebs_configuration) > 0:
# Key that should be extracted
ebs_optimized = "ebs_optimized"
ebs_block_device_configs = "ebs_block_device_configs"
volume_specification = "volume_specification"
size_in_gb = "size_in_gb"
volume_type = "volume_type"
iops = "iops"
volumes_per_instance = "volumes_per_instance"
key_ebs_optimized = "{0}._{1}".format(key_ebs_config, ebs_optimized)
# EbsOptimized config
if key_ebs_optimized in ebs_configuration:
instance_group.pop(key_ebs_optimized)
ebs_configuration[ebs_optimized] = ebs_configuration.pop(
key_ebs_optimized
)
# Ebs Blocks
ebs_blocks = []
idx = 1
keyfmt = "{0}._{1}.member.{{}}".format(
key_ebs_config, ebs_block_device_configs
)
key = keyfmt.format(idx)
while self._has_key_prefix(key, ebs_configuration):
vlespc_keyfmt = "{0}._{1}._{{}}".format(key, volume_specification)
vol_size = vlespc_keyfmt.format(size_in_gb)
vol_iops = vlespc_keyfmt.format(iops)
vol_type = vlespc_keyfmt.format(volume_type)
ebs_block = dict()
ebs_block[volume_specification] = dict()
if vol_size in ebs_configuration:
instance_group.pop(vol_size)
ebs_block[volume_specification][size_in_gb] = int(
ebs_configuration.pop(vol_size)
)
if vol_iops in ebs_configuration:
instance_group.pop(vol_iops)
ebs_block[volume_specification][iops] = ebs_configuration.pop(
vol_iops
)
if vol_type in ebs_configuration:
instance_group.pop(vol_type)
ebs_block[volume_specification][
volume_type
] = ebs_configuration.pop(vol_type)
per_instance = "{0}._{1}".format(key, volumes_per_instance)
if per_instance in ebs_configuration:
instance_group.pop(per_instance)
ebs_block[volumes_per_instance] = int(
ebs_configuration.pop(per_instance)
)
if len(ebs_block) > 0:
ebs_blocks.append(ebs_block)
idx += 1
key = keyfmt.format(idx)
if len(ebs_blocks) > 0:
ebs_configuration[ebs_block_device_configs] = ebs_blocks
instance_group[key_ebs_config] = ebs_configuration
@generate_boto3_response("SetTerminationProtection")
def set_termination_protection(self):
termination_protection = self._get_param("TerminationProtected")
@ -754,7 +837,22 @@ LIST_INSTANCE_GROUPS_TEMPLATE = """<ListInstanceGroupsResponse xmlns="http://ela
<BidPrice>{{ instance_group.bid_price }}</BidPrice>
{% endif %}
<Configurations/>
<EbsBlockDevices/>
{% if instance_group.ebs_configuration is not none %}
<EbsBlockDevices>
{% for ebs_block_device in instance_group.ebs_configuration.ebs_block_device_configs %}
{% for i in range(ebs_block_device.volumes_per_instance) %}
<member>
<VolumeSpecification>
<VolumeType>{{ebs_block_device.volume_specification.volume_type}}</VolumeType>
<Iops>{{ebs_block_device.volume_specification.iops}}</Iops>
<SizeInGB>{{ebs_block_device.volume_specification.size_in_gb}}</SizeInGB>
</VolumeSpecification>
<Device>/dev/sd{{i}}</Device>
</member>
{% endfor %}
{% endfor %}
</EbsBlockDevices>
{% endif %}
{% if instance_group.ebs_optimized is not none %}
<EbsOptimized>{{ instance_group.ebs_optimized }}</EbsOptimized>
{% endif %}

View File

@ -70,6 +70,7 @@ def lambda_handler(event, context):
def get_test_zip_file3():
pfunc = """
def lambda_handler(event, context):
print("Nr_of_records("+str(len(event['Records']))+")")
print("get_test_zip_file3 success")
return event
"""
@ -1111,7 +1112,6 @@ def test_create_event_source_mapping():
@mock_lambda
@mock_sqs
def test_invoke_function_from_sqs():
logs_conn = boto3.client("logs", region_name="us-east-1")
sqs = boto3.resource("sqs", region_name="us-east-1")
queue = sqs.create_queue(QueueName="test-sqs-queue1")
@ -1137,32 +1137,22 @@ def test_invoke_function_from_sqs():
sqs_client = boto3.client("sqs", region_name="us-east-1")
sqs_client.send_message(QueueUrl=queue.url, MessageBody="test")
start = time.time()
while (time.time() - start) < 30:
result = logs_conn.describe_log_streams(logGroupName="/aws/lambda/testFunction")
log_streams = result.get("logStreams")
if not log_streams:
time.sleep(1)
continue
assert len(log_streams) == 1
result = logs_conn.get_log_events(
logGroupName="/aws/lambda/testFunction",
logStreamName=log_streams[0]["logStreamName"],
)
for event in result.get("events"):
if event["message"] == "get_test_zip_file3 success":
return
time.sleep(1)
expected_msg = "get_test_zip_file3 success"
log_group = "/aws/lambda/testFunction"
msg_showed_up, all_logs = wait_for_log_msg(expected_msg, log_group)
assert False, "Test Failed"
assert msg_showed_up, (
expected_msg
+ " was not found after sending an SQS message. All logs: "
+ all_logs
)
@mock_logs
@mock_lambda
@mock_dynamodb2
def test_invoke_function_from_dynamodb_put():
logs_conn = boto3.client("logs", region_name="us-east-1")
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
table_name = "table_with_stream"
table = dynamodb.create_table(
@ -1197,32 +1187,20 @@ def test_invoke_function_from_dynamodb_put():
assert response["State"] == "Enabled"
dynamodb.put_item(TableName=table_name, Item={"id": {"S": "item 1"}})
start = time.time()
while (time.time() - start) < 30:
result = logs_conn.describe_log_streams(logGroupName="/aws/lambda/testFunction")
log_streams = result.get("logStreams")
if not log_streams:
time.sleep(1)
continue
assert len(log_streams) == 1
result = logs_conn.get_log_events(
logGroupName="/aws/lambda/testFunction",
logStreamName=log_streams[0]["logStreamName"],
)
for event in result.get("events"):
if event["message"] == "get_test_zip_file3 success":
return
time.sleep(1)
expected_msg = "get_test_zip_file3 success"
log_group = "/aws/lambda/testFunction"
msg_showed_up, all_logs = wait_for_log_msg(expected_msg, log_group)
assert False, "Test Failed"
assert msg_showed_up, (
expected_msg + " was not found after a DDB insert. All logs: " + all_logs
)
@mock_logs
@mock_lambda
@mock_dynamodb2
def test_invoke_function_from_dynamodb_update():
logs_conn = boto3.client("logs", region_name="us-east-1")
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
table_name = "table_with_stream"
table = dynamodb.create_table(
@ -1234,7 +1212,6 @@ def test_invoke_function_from_dynamodb_update():
"StreamViewType": "NEW_AND_OLD_IMAGES",
},
)
dynamodb.put_item(TableName=table_name, Item={"id": {"S": "item 1"}})
conn = boto3.client("lambda", region_name="us-east-1")
func = conn.create_function(
@ -1249,13 +1226,17 @@ def test_invoke_function_from_dynamodb_update():
Publish=True,
)
response = conn.create_event_source_mapping(
conn.create_event_source_mapping(
EventSourceArn=table["TableDescription"]["LatestStreamArn"],
FunctionName=func["FunctionArn"],
)
assert response["EventSourceArn"] == table["TableDescription"]["LatestStreamArn"]
assert response["State"] == "Enabled"
dynamodb.put_item(TableName=table_name, Item={"id": {"S": "item 1"}})
log_group = "/aws/lambda/testFunction"
expected_msg = "get_test_zip_file3 success"
msg_showed_up, all_logs = wait_for_log_msg(expected_msg, log_group)
assert "Nr_of_records(1)" in all_logs, "Only one item should be inserted"
dynamodb.update_item(
TableName=table_name,
Key={"id": {"S": "item 1"}},
@ -1263,25 +1244,39 @@ def test_invoke_function_from_dynamodb_update():
ExpressionAttributeNames={"#attr": "new_attr"},
ExpressionAttributeValues={":val": {"S": "new_val"}},
)
msg_showed_up, all_logs = wait_for_log_msg(expected_msg, log_group)
assert msg_showed_up, (
expected_msg + " was not found after updating DDB. All logs: " + str(all_logs)
)
assert "Nr_of_records(1)" in all_logs, "Only one item should be updated"
assert (
"Nr_of_records(2)" not in all_logs
), "The inserted item should not show up again"
def wait_for_log_msg(expected_msg, log_group):
logs_conn = boto3.client("logs", region_name="us-east-1")
received_messages = []
start = time.time()
while (time.time() - start) < 30:
result = logs_conn.describe_log_streams(logGroupName="/aws/lambda/testFunction")
while (time.time() - start) < 10:
result = logs_conn.describe_log_streams(logGroupName=log_group)
log_streams = result.get("logStreams")
if not log_streams:
time.sleep(1)
continue
assert len(log_streams) == 1
result = logs_conn.get_log_events(
logGroupName="/aws/lambda/testFunction",
logStreamName=log_streams[0]["logStreamName"],
)
for event in result.get("events"):
if event["message"] == "get_test_zip_file3 success":
return
for log_stream in log_streams:
result = logs_conn.get_log_events(
logGroupName=log_group, logStreamName=log_stream["logStreamName"],
)
received_messages.extend(
[event["message"] for event in result.get("events")]
)
if expected_msg in received_messages:
return True, received_messages
time.sleep(1)
assert False, "Test Failed"
return False, received_messages
@mock_logs

View File

@ -1,8 +1,10 @@
import json
import os
from datetime import datetime, timedelta
import boto3
from botocore.exceptions import ClientError
from nose import SkipTest
from nose.tools import assert_raises
from moto import mock_s3
@ -1802,3 +1804,71 @@ def test_batch_get_aggregate_resource_config():
len(result["UnprocessedResourceIdentifiers"]) == 1
and result["UnprocessedResourceIdentifiers"][0]["SourceRegion"] == "eu-west-1"
)
@mock_config
def test_put_evaluations():
client = boto3.client("config", region_name="us-west-2")
# Try without Evaluations supplied:
with assert_raises(ClientError) as ce:
client.put_evaluations(Evaluations=[], ResultToken="test", TestMode=True)
assert ce.exception.response["Error"]["Code"] == "InvalidParameterValueException"
assert (
"The Evaluations object in your request cannot be null"
in ce.exception.response["Error"]["Message"]
)
# Try without a ResultToken supplied:
with assert_raises(ClientError) as ce:
client.put_evaluations(
Evaluations=[
{
"ComplianceResourceType": "AWS::ApiGateway::RestApi",
"ComplianceResourceId": "test-api",
"ComplianceType": "INSUFFICIENT_DATA",
"OrderingTimestamp": datetime(2015, 1, 1),
}
],
ResultToken="",
TestMode=True,
)
assert ce.exception.response["Error"]["Code"] == "InvalidResultTokenException"
if os.environ.get("TEST_SERVER_MODE", "false").lower() == "true":
raise SkipTest("Does not work in server mode due to error in Workzeug")
else:
# Try without TestMode supplied:
with assert_raises(NotImplementedError):
client.put_evaluations(
Evaluations=[
{
"ComplianceResourceType": "AWS::ApiGateway::RestApi",
"ComplianceResourceId": "test-api",
"ComplianceType": "INSUFFICIENT_DATA",
"OrderingTimestamp": datetime(2015, 1, 1),
}
],
ResultToken="test",
)
# Now with proper params:
response = client.put_evaluations(
Evaluations=[
{
"ComplianceResourceType": "AWS::ApiGateway::RestApi",
"ComplianceResourceId": "test-api",
"ComplianceType": "INSUFFICIENT_DATA",
"OrderingTimestamp": datetime(2015, 1, 1),
}
],
TestMode=True,
ResultToken="test",
)
# this is hard to match against, so remove it
response["ResponseMetadata"].pop("HTTPHeaders", None)
response["ResponseMetadata"].pop("RetryAttempts", None)
response.should.equal(
{"FailedEvaluations": [], "ResponseMetadata": {"HTTPStatusCode": 200,},}
)

View File

@ -1144,7 +1144,7 @@ def test_describe_instance_status_with_instances():
@mock_ec2_deprecated
def test_describe_instance_status_with_instance_filter():
def test_describe_instance_status_with_instance_filter_deprecated():
conn = boto.connect_ec2("the_key", "the_secret")
# We want to filter based on this one
@ -1166,6 +1166,75 @@ def test_describe_instance_status_with_instance_filter():
cm.exception.request_id.should_not.be.none
@mock_ec2
def test_describe_instance_status_with_instance_filter():
conn = boto3.client("ec2", region_name="us-west-1")
# We want to filter based on this one
reservation = conn.run_instances(ImageId="ami-1234abcd", MinCount=3, MaxCount=3)
instance1 = reservation["Instances"][0]
instance2 = reservation["Instances"][1]
instance3 = reservation["Instances"][2]
conn.stop_instances(InstanceIds=[instance1["InstanceId"]])
stopped_instance_ids = [instance1["InstanceId"]]
running_instance_ids = sorted([instance2["InstanceId"], instance3["InstanceId"]])
all_instance_ids = sorted(stopped_instance_ids + running_instance_ids)
# Filter instance using the state name
state_name_filter = {
"running_and_stopped": [
{"Name": "instance-state-name", "Values": ["running", "stopped"]}
],
"running": [{"Name": "instance-state-name", "Values": ["running"]}],
"stopped": [{"Name": "instance-state-name", "Values": ["stopped"]}],
}
found_statuses = conn.describe_instance_status(
IncludeAllInstances=True, Filters=state_name_filter["running_and_stopped"]
)["InstanceStatuses"]
found_instance_ids = [status["InstanceId"] for status in found_statuses]
sorted(found_instance_ids).should.equal(all_instance_ids)
found_statuses = conn.describe_instance_status(
IncludeAllInstances=True, Filters=state_name_filter["running"]
)["InstanceStatuses"]
found_instance_ids = [status["InstanceId"] for status in found_statuses]
sorted(found_instance_ids).should.equal(running_instance_ids)
found_statuses = conn.describe_instance_status(
IncludeAllInstances=True, Filters=state_name_filter["stopped"]
)["InstanceStatuses"]
found_instance_ids = [status["InstanceId"] for status in found_statuses]
sorted(found_instance_ids).should.equal(stopped_instance_ids)
# Filter instance using the state code
state_code_filter = {
"running_and_stopped": [
{"Name": "instance-state-code", "Values": ["16", "80"]}
],
"running": [{"Name": "instance-state-code", "Values": ["16"]}],
"stopped": [{"Name": "instance-state-code", "Values": ["80"]}],
}
found_statuses = conn.describe_instance_status(
IncludeAllInstances=True, Filters=state_code_filter["running_and_stopped"]
)["InstanceStatuses"]
found_instance_ids = [status["InstanceId"] for status in found_statuses]
sorted(found_instance_ids).should.equal(all_instance_ids)
found_statuses = conn.describe_instance_status(
IncludeAllInstances=True, Filters=state_code_filter["running"]
)["InstanceStatuses"]
found_instance_ids = [status["InstanceId"] for status in found_statuses]
sorted(found_instance_ids).should.equal(running_instance_ids)
found_statuses = conn.describe_instance_status(
IncludeAllInstances=True, Filters=state_code_filter["stopped"]
)["InstanceStatuses"]
found_instance_ids = [status["InstanceId"] for status in found_statuses]
sorted(found_instance_ids).should.equal(stopped_instance_ids)
@requires_boto_gte("2.32.0")
@mock_ec2_deprecated
def test_describe_instance_status_with_non_running_instances():

View File

@ -123,6 +123,19 @@ def test_create_two_security_groups_with_same_name_in_different_vpc():
set(group_names).should.equal(set(["default", "test security group"]))
@mock_ec2
def test_create_two_security_groups_in_vpc_with_ipv6_enabled():
ec2 = boto3.resource("ec2", region_name="us-west-1")
vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16", AmazonProvidedIpv6CidrBlock=True)
security_group = ec2.create_security_group(
GroupName="sg01", Description="Test security group sg01", VpcId=vpc.id
)
# The security group must have two defaul egress rules (one for ipv4 and aonther for ipv6)
security_group.ip_permissions_egress.should.have.length_of(2)
@mock_ec2_deprecated
def test_deleting_security_groups():
conn = boto.connect_ec2("the_key", "the_secret")

View File

@ -538,7 +538,7 @@ def test_describe_image_that_doesnt_exist():
repositoryName="test_repository",
imageIds=[{"imageTag": "testtag"}],
registryId="123",
).should.throw(ClientError, error_msg1)
).should.throw(client.exceptions.ImageNotFoundException, error_msg1)
error_msg2 = re.compile(
r".*The repository with name 'repo-that-doesnt-exist' does not exist in the registry with id '123'.*",

View File

@ -60,6 +60,15 @@ input_instance_groups = [
"Market": "SPOT",
"Name": "task-2",
"BidPrice": "0.05",
"EbsConfiguration": {
"EbsBlockDeviceConfigs": [
{
"VolumeSpecification": {"VolumeType": "gp2", "SizeInGB": 800},
"VolumesPerInstance": 6,
},
],
"EbsOptimized": True,
},
},
]
@ -430,6 +439,21 @@ def test_run_job_flow_with_visible_to_all_users():
resp["Cluster"]["VisibleToAllUsers"].should.equal(expected)
def _do_assertion_ebs_configuration(x, y):
total_volumes = 0
total_size = 0
for ebs_block in y["EbsConfiguration"]["EbsBlockDeviceConfigs"]:
total_volumes += ebs_block["VolumesPerInstance"]
total_size += ebs_block["VolumeSpecification"]["SizeInGB"]
# Multiply by total volumes
total_size = total_size * total_volumes
comp_total_size = 0
for ebs_block in x["EbsBlockDevices"]:
comp_total_size += ebs_block["VolumeSpecification"]["SizeInGB"]
len(x["EbsBlockDevices"]).should.equal(total_volumes)
comp_total_size.should.equal(comp_total_size)
@mock_emr
def test_run_job_flow_with_instance_groups():
input_groups = dict((g["Name"], g) for g in input_instance_groups)
@ -448,6 +472,9 @@ def test_run_job_flow_with_instance_groups():
if "BidPrice" in y:
x["BidPrice"].should.equal(y["BidPrice"])
if "EbsConfiguration" in y:
_do_assertion_ebs_configuration(x, y)
@mock_emr
def test_run_job_flow_with_custom_ami():
@ -623,6 +650,8 @@ def test_instance_groups():
y = input_groups[x["Name"]]
if hasattr(y, "BidPrice"):
x["BidPrice"].should.equal("BidPrice")
if "EbsConfiguration" in y:
_do_assertion_ebs_configuration(x, y)
# Configurations
# EbsBlockDevices
# EbsOptimized

View File

@ -39,6 +39,7 @@ import moto.s3.models as s3model
from moto.core.exceptions import InvalidNextTokenException
from moto.core.utils import py2_strip_unicode_keys
if settings.TEST_SERVER_MODE:
REDUCED_PART_SIZE = s3model.UPLOAD_PART_MIN_SIZE
EXPECTED_ETAG = '"140f92a6df9f9e415f74a1463bcee9bb-2"'
@ -1018,12 +1019,23 @@ def test_s3_object_in_public_bucket():
s3_anonymous.Object(key="file.txt", bucket_name="test-bucket").get()
exc.exception.response["Error"]["Code"].should.equal("403")
@mock_s3
def test_s3_object_in_public_bucket_using_multiple_presigned_urls():
s3 = boto3.resource("s3")
bucket = s3.Bucket("test-bucket")
bucket.create(
ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "us-west-1"}
)
bucket.put_object(Body=b"ABCD", Key="file.txt")
params = {"Bucket": "test-bucket", "Key": "file.txt"}
presigned_url = boto3.client("s3").generate_presigned_url(
"get_object", params, ExpiresIn=900
)
response = requests.get(presigned_url)
assert response.status_code == 200
for i in range(1, 10):
response = requests.get(presigned_url)
assert response.status_code == 200, "Failed on req number {}".format(i)
@mock_s3