Add default ecs attributes and format in response obj (#1346)
* Initialize EC2ContainerServiceBackend and ContainerInstance objects with region_name * Initialize ContainerInstance with default attributes * These attributes are automatically applied by ECS when a container is registered * Docs: http://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-constraints.html#attributes * Format container_instance.attributes for response_object * Python3 * Only use available ECS regions for ecs_backends * Sort dictionaries on key='name' using lambda * Sort all dicts in tests using lambda
This commit is contained in:
parent
caec929506
commit
04c5198a0c
@ -47,3 +47,4 @@ Moto is written by Steve Pulec with contributions from:
|
|||||||
* [Adam Stauffer](https://github.com/adamstauffer)
|
* [Adam Stauffer](https://github.com/adamstauffer)
|
||||||
* [Guy Templeton](https://github.com/gjtempleton)
|
* [Guy Templeton](https://github.com/gjtempleton)
|
||||||
* [Michael van Tellingen](https://github.com/mvantellingen)
|
* [Michael van Tellingen](https://github.com/mvantellingen)
|
||||||
|
* [Jessie Nadler](https://github.com/nadlerjessie)
|
||||||
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from random import random, randint
|
from random import random, randint
|
||||||
|
import boto3
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from moto.core.exceptions import JsonRESTError
|
from moto.core.exceptions import JsonRESTError
|
||||||
@ -261,7 +262,7 @@ class Service(BaseObject):
|
|||||||
|
|
||||||
class ContainerInstance(BaseObject):
|
class ContainerInstance(BaseObject):
|
||||||
|
|
||||||
def __init__(self, ec2_instance_id):
|
def __init__(self, ec2_instance_id, region_name):
|
||||||
self.ec2_instance_id = ec2_instance_id
|
self.ec2_instance_id = ec2_instance_id
|
||||||
self.agent_connected = True
|
self.agent_connected = True
|
||||||
self.status = 'ACTIVE'
|
self.status = 'ACTIVE'
|
||||||
@ -321,14 +322,29 @@ class ContainerInstance(BaseObject):
|
|||||||
'agentHash': '4023248',
|
'agentHash': '4023248',
|
||||||
'dockerVersion': 'DockerVersion: 1.5.0'
|
'dockerVersion': 'DockerVersion: 1.5.0'
|
||||||
}
|
}
|
||||||
|
ec2_backend = ec2_backends[region_name]
|
||||||
self.attributes = {}
|
ec2_instance = ec2_backend.get_instance(ec2_instance_id)
|
||||||
|
self.attributes = {
|
||||||
|
'ecs.ami-id': ec2_instance.image_id,
|
||||||
|
'ecs.availability-zone': ec2_instance.placement,
|
||||||
|
'ecs.instance-type': ec2_instance.instance_type,
|
||||||
|
'ecs.os-type': ec2_instance.platform if ec2_instance.platform == 'windows' else 'linux' # options are windows and linux, linux is default
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def response_object(self):
|
def response_object(self):
|
||||||
response_object = self.gen_response_object()
|
response_object = self.gen_response_object()
|
||||||
|
response_object['attributes'] = [self._format_attribute(name, value) for name, value in response_object['attributes'].items()]
|
||||||
return response_object
|
return response_object
|
||||||
|
|
||||||
|
def _format_attribute(self, name, value):
|
||||||
|
formatted_attr = {
|
||||||
|
'name': name,
|
||||||
|
}
|
||||||
|
if value is not None:
|
||||||
|
formatted_attr['value'] = value
|
||||||
|
return formatted_attr
|
||||||
|
|
||||||
|
|
||||||
class ContainerInstanceFailure(BaseObject):
|
class ContainerInstanceFailure(BaseObject):
|
||||||
|
|
||||||
@ -347,12 +363,19 @@ class ContainerInstanceFailure(BaseObject):
|
|||||||
|
|
||||||
class EC2ContainerServiceBackend(BaseBackend):
|
class EC2ContainerServiceBackend(BaseBackend):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, region_name):
|
||||||
|
super(EC2ContainerServiceBackend, self).__init__()
|
||||||
self.clusters = {}
|
self.clusters = {}
|
||||||
self.task_definitions = {}
|
self.task_definitions = {}
|
||||||
self.tasks = {}
|
self.tasks = {}
|
||||||
self.services = {}
|
self.services = {}
|
||||||
self.container_instances = {}
|
self.container_instances = {}
|
||||||
|
self.region_name = region_name
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
region_name = self.region_name
|
||||||
|
self.__dict__ = {}
|
||||||
|
self.__init__(region_name)
|
||||||
|
|
||||||
def describe_task_definition(self, task_definition_str):
|
def describe_task_definition(self, task_definition_str):
|
||||||
task_definition_name = task_definition_str.split('/')[-1]
|
task_definition_name = task_definition_str.split('/')[-1]
|
||||||
@ -669,7 +692,7 @@ class EC2ContainerServiceBackend(BaseBackend):
|
|||||||
cluster_name = cluster_str.split('/')[-1]
|
cluster_name = cluster_str.split('/')[-1]
|
||||||
if cluster_name not in self.clusters:
|
if cluster_name not in self.clusters:
|
||||||
raise Exception("{0} is not a cluster".format(cluster_name))
|
raise Exception("{0} is not a cluster".format(cluster_name))
|
||||||
container_instance = ContainerInstance(ec2_instance_id)
|
container_instance = ContainerInstance(ec2_instance_id, self.region_name)
|
||||||
if not self.container_instances.get(cluster_name):
|
if not self.container_instances.get(cluster_name):
|
||||||
self.container_instances[cluster_name] = {}
|
self.container_instances[cluster_name] = {}
|
||||||
container_instance_id = container_instance.container_instance_arn.split(
|
container_instance_id = container_instance.container_instance_arn.split(
|
||||||
@ -866,6 +889,5 @@ class EC2ContainerServiceBackend(BaseBackend):
|
|||||||
yield task_fam
|
yield task_fam
|
||||||
|
|
||||||
|
|
||||||
ecs_backends = {}
|
available_regions = boto3.session.Session().get_available_regions("ecs")
|
||||||
for region, ec2_backend in ec2_backends.items():
|
ecs_backends = {region: EC2ContainerServiceBackend(region) for region in available_regions}
|
||||||
ecs_backends[region] = EC2ContainerServiceBackend()
|
|
||||||
|
@ -1624,11 +1624,13 @@ def test_attributes():
|
|||||||
clusterName=test_cluster_name
|
clusterName=test_cluster_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
instances = []
|
||||||
test_instance = ec2.create_instances(
|
test_instance = ec2.create_instances(
|
||||||
ImageId="ami-1234abcd",
|
ImageId="ami-1234abcd",
|
||||||
MinCount=1,
|
MinCount=1,
|
||||||
MaxCount=1,
|
MaxCount=1,
|
||||||
)[0]
|
)[0]
|
||||||
|
instances.append(test_instance)
|
||||||
|
|
||||||
instance_id_document = json.dumps(
|
instance_id_document = json.dumps(
|
||||||
ec2_utils.generate_instance_identity_document(test_instance)
|
ec2_utils.generate_instance_identity_document(test_instance)
|
||||||
@ -1648,6 +1650,7 @@ def test_attributes():
|
|||||||
MinCount=1,
|
MinCount=1,
|
||||||
MaxCount=1,
|
MaxCount=1,
|
||||||
)[0]
|
)[0]
|
||||||
|
instances.append(test_instance)
|
||||||
|
|
||||||
instance_id_document = json.dumps(
|
instance_id_document = json.dumps(
|
||||||
ec2_utils.generate_instance_identity_document(test_instance)
|
ec2_utils.generate_instance_identity_document(test_instance)
|
||||||
@ -1680,7 +1683,10 @@ def test_attributes():
|
|||||||
targetType='container-instance'
|
targetType='container-instance'
|
||||||
)
|
)
|
||||||
attrs = resp['attributes']
|
attrs = resp['attributes']
|
||||||
len(attrs).should.equal(4)
|
|
||||||
|
NUM_CUSTOM_ATTRIBUTES = 4 # 2 specific to individual machines and 1 global, going to both machines (2 + 1*2)
|
||||||
|
NUM_DEFAULT_ATTRIBUTES = 4
|
||||||
|
len(attrs).should.equal(NUM_CUSTOM_ATTRIBUTES + (NUM_DEFAULT_ATTRIBUTES * len(instances)))
|
||||||
|
|
||||||
# Tests that the attrs have been set properly
|
# Tests that the attrs have been set properly
|
||||||
len(list(filter(lambda item: item['name'] == 'env', attrs))).should.equal(2)
|
len(list(filter(lambda item: item['name'] == 'env', attrs))).should.equal(2)
|
||||||
@ -1692,13 +1698,14 @@ def test_attributes():
|
|||||||
{'name': 'attr1', 'value': 'instance2', 'targetId': partial_arn2, 'targetType': 'container-instance'}
|
{'name': 'attr1', 'value': 'instance2', 'targetId': partial_arn2, 'targetType': 'container-instance'}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
NUM_CUSTOM_ATTRIBUTES -= 1
|
||||||
|
|
||||||
resp = ecs_client.list_attributes(
|
resp = ecs_client.list_attributes(
|
||||||
cluster=test_cluster_name,
|
cluster=test_cluster_name,
|
||||||
targetType='container-instance'
|
targetType='container-instance'
|
||||||
)
|
)
|
||||||
attrs = resp['attributes']
|
attrs = resp['attributes']
|
||||||
len(attrs).should.equal(3)
|
len(attrs).should.equal(NUM_CUSTOM_ATTRIBUTES + (NUM_DEFAULT_ATTRIBUTES * len(instances)))
|
||||||
|
|
||||||
|
|
||||||
@mock_ecs
|
@mock_ecs
|
||||||
@ -1757,6 +1764,114 @@ def test_list_task_definition_families():
|
|||||||
len(resp2['families']).should.equal(1)
|
len(resp2['families']).should.equal(1)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
@mock_ecs
|
||||||
|
def test_default_container_instance_attributes():
|
||||||
|
ecs_client = boto3.client('ecs', region_name='us-east-1')
|
||||||
|
ec2 = boto3.resource('ec2', region_name='us-east-1')
|
||||||
|
|
||||||
|
test_cluster_name = 'test_ecs_cluster'
|
||||||
|
|
||||||
|
# Create cluster and EC2 instance
|
||||||
|
_ = ecs_client.create_cluster(
|
||||||
|
clusterName=test_cluster_name
|
||||||
|
)
|
||||||
|
|
||||||
|
test_instance = ec2.create_instances(
|
||||||
|
ImageId="ami-1234abcd",
|
||||||
|
MinCount=1,
|
||||||
|
MaxCount=1,
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
instance_id_document = json.dumps(
|
||||||
|
ec2_utils.generate_instance_identity_document(test_instance)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register container instance
|
||||||
|
response = ecs_client.register_container_instance(
|
||||||
|
cluster=test_cluster_name,
|
||||||
|
instanceIdentityDocument=instance_id_document
|
||||||
|
)
|
||||||
|
|
||||||
|
response['containerInstance'][
|
||||||
|
'ec2InstanceId'].should.equal(test_instance.id)
|
||||||
|
full_arn = response['containerInstance']['containerInstanceArn']
|
||||||
|
container_instance_id = full_arn.rsplit('/', 1)[-1]
|
||||||
|
|
||||||
|
default_attributes = response['containerInstance']['attributes']
|
||||||
|
assert len(default_attributes) == 4
|
||||||
|
expected_result = [
|
||||||
|
{'name': 'ecs.availability-zone', 'value': test_instance.placement['AvailabilityZone']},
|
||||||
|
{'name': 'ecs.ami-id', 'value': test_instance.image_id},
|
||||||
|
{'name': 'ecs.instance-type', 'value': test_instance.instance_type},
|
||||||
|
{'name': 'ecs.os-type', 'value': test_instance.platform or 'linux'}
|
||||||
|
]
|
||||||
|
assert sorted(default_attributes, key=lambda item: item['name']) == sorted(expected_result, key=lambda item: item['name'])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
@mock_ecs
|
||||||
|
def test_describe_container_instances_with_attributes():
|
||||||
|
ecs_client = boto3.client('ecs', region_name='us-east-1')
|
||||||
|
ec2 = boto3.resource('ec2', region_name='us-east-1')
|
||||||
|
|
||||||
|
test_cluster_name = 'test_ecs_cluster'
|
||||||
|
|
||||||
|
# Create cluster and EC2 instance
|
||||||
|
_ = ecs_client.create_cluster(
|
||||||
|
clusterName=test_cluster_name
|
||||||
|
)
|
||||||
|
|
||||||
|
test_instance = ec2.create_instances(
|
||||||
|
ImageId="ami-1234abcd",
|
||||||
|
MinCount=1,
|
||||||
|
MaxCount=1,
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
instance_id_document = json.dumps(
|
||||||
|
ec2_utils.generate_instance_identity_document(test_instance)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register container instance
|
||||||
|
response = ecs_client.register_container_instance(
|
||||||
|
cluster=test_cluster_name,
|
||||||
|
instanceIdentityDocument=instance_id_document
|
||||||
|
)
|
||||||
|
|
||||||
|
response['containerInstance'][
|
||||||
|
'ec2InstanceId'].should.equal(test_instance.id)
|
||||||
|
full_arn = response['containerInstance']['containerInstanceArn']
|
||||||
|
container_instance_id = full_arn.rsplit('/', 1)[-1]
|
||||||
|
default_attributes = response['containerInstance']['attributes']
|
||||||
|
|
||||||
|
# Set attributes on container instance, one without a value
|
||||||
|
attributes = [
|
||||||
|
{'name': 'env', 'value': 'prod'},
|
||||||
|
{'name': 'attr1', 'value': 'instance1', 'targetId': container_instance_id, 'targetType': 'container-instance'},
|
||||||
|
{'name': 'attr_without_value'}
|
||||||
|
]
|
||||||
|
ecs_client.put_attributes(
|
||||||
|
cluster=test_cluster_name,
|
||||||
|
attributes=attributes
|
||||||
|
)
|
||||||
|
|
||||||
|
# Describe container instance, should have attributes previously set
|
||||||
|
described_instance = ecs_client.describe_container_instances(cluster=test_cluster_name, containerInstances=[container_instance_id])
|
||||||
|
|
||||||
|
assert len(described_instance['containerInstances']) == 1
|
||||||
|
assert isinstance(described_instance['containerInstances'][0]['attributes'], list)
|
||||||
|
|
||||||
|
# Remove additional info passed to put_attributes
|
||||||
|
cleaned_attributes = []
|
||||||
|
for attribute in attributes:
|
||||||
|
attribute.pop('targetId', None)
|
||||||
|
attribute.pop('targetType', None)
|
||||||
|
cleaned_attributes.append(attribute)
|
||||||
|
described_attributes = sorted(described_instance['containerInstances'][0]['attributes'], key=lambda item: item['name'])
|
||||||
|
expected_attributes = sorted(default_attributes + cleaned_attributes, key=lambda item: item['name'])
|
||||||
|
assert described_attributes == expected_attributes
|
||||||
|
|
||||||
|
|
||||||
def _fetch_container_instance_resources(container_instance_description):
|
def _fetch_container_instance_resources(container_instance_description):
|
||||||
remaining_resources = {}
|
remaining_resources = {}
|
||||||
registered_resources = {}
|
registered_resources = {}
|
||||||
|
Loading…
Reference in New Issue
Block a user