Merge pull request #635 from riccardomc/master
Introduce ContainerInstance handling in ECS
This commit is contained in:
commit
57167b1890
@ -515,3 +515,35 @@ def is_valid_cidr(cird):
|
|||||||
cidr_pattern = '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$'
|
cidr_pattern = '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$'
|
||||||
cidr_pattern_re = re.compile(cidr_pattern)
|
cidr_pattern_re = re.compile(cidr_pattern)
|
||||||
return cidr_pattern_re.match(cird) is not None
|
return cidr_pattern_re.match(cird) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_instance_identity_document(instance):
|
||||||
|
"""
|
||||||
|
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
|
||||||
|
|
||||||
|
A JSON file that describes an instance. Usually retrieved by URL:
|
||||||
|
http://169.254.169.254/latest/dynamic/instance-identity/document
|
||||||
|
Here we just fill a dictionary that represents the document
|
||||||
|
|
||||||
|
Typically, this document is used by the amazon-ecs-agent when registering a
|
||||||
|
new ContainerInstance
|
||||||
|
"""
|
||||||
|
|
||||||
|
document = {
|
||||||
|
'devPayProductCodes': None,
|
||||||
|
'availabilityZone': instance.placement['AvailabilityZone'],
|
||||||
|
'privateIp': instance.private_ip_address,
|
||||||
|
'version': '2010-8-31',
|
||||||
|
'region': instance.placement['AvailabilityZone'][:-1],
|
||||||
|
'instanceId': instance.id,
|
||||||
|
'billingProducts': None,
|
||||||
|
'instanceType': instance.instance_type,
|
||||||
|
'accountId': '012345678910',
|
||||||
|
'pendingTime': '2015-11-19T16:32:11Z',
|
||||||
|
'imageId': instance.image_id,
|
||||||
|
'kernelId': instance.kernel_id,
|
||||||
|
'ramdiskId': instance.ramdisk_id,
|
||||||
|
'architecture': instance.architecture,
|
||||||
|
}
|
||||||
|
|
||||||
|
return document
|
||||||
|
@ -85,11 +85,48 @@ class Service(BaseObject):
|
|||||||
return response_object
|
return response_object
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerInstance(BaseObject):
|
||||||
|
def __init__(self, ec2_instance_id):
|
||||||
|
self.ec2_instance_id = ec2_instance_id
|
||||||
|
self.status = 'ACTIVE'
|
||||||
|
self.registeredResources = []
|
||||||
|
self.agentConnected = True
|
||||||
|
self.containerInstanceArn = "arn:aws:ecs:us-east-1:012345678910:container-instance/{0}".format(str(uuid.uuid1()))
|
||||||
|
self.pendingTaskCount = 0
|
||||||
|
self.remainingResources = []
|
||||||
|
self.runningTaskCount = 0
|
||||||
|
self.versionInfo = {
|
||||||
|
'agentVersion': "1.0.0",
|
||||||
|
'agentHash': '4023248',
|
||||||
|
'dockerVersion': 'DockerVersion: 1.5.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def response_object(self):
|
||||||
|
response_object = self.gen_response_object()
|
||||||
|
del response_object['name'], response_object['arn']
|
||||||
|
return response_object
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerInstanceFailure(BaseObject):
|
||||||
|
def __init__(self, reason, container_instance_id):
|
||||||
|
self.reason = reason
|
||||||
|
self.arn = "arn:aws:ecs:us-east-1:012345678910:container-instance/{0}".format(container_instance_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def response_object(self):
|
||||||
|
response_object = self.gen_response_object()
|
||||||
|
response_object['reason'] = self.reason
|
||||||
|
response_object['arn'] = self.arn
|
||||||
|
return response_object
|
||||||
|
|
||||||
|
|
||||||
class EC2ContainerServiceBackend(BaseBackend):
|
class EC2ContainerServiceBackend(BaseBackend):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.clusters = {}
|
self.clusters = {}
|
||||||
self.task_definitions = {}
|
self.task_definitions = {}
|
||||||
self.services = {}
|
self.services = {}
|
||||||
|
self.container_instances = {}
|
||||||
|
|
||||||
def fetch_task_definition(self, task_definition_str):
|
def fetch_task_definition(self, task_definition_str):
|
||||||
task_definition_components = task_definition_str.split(':')
|
task_definition_components = task_definition_str.split(':')
|
||||||
@ -212,6 +249,41 @@ class EC2ContainerServiceBackend(BaseBackend):
|
|||||||
else:
|
else:
|
||||||
raise Exception("cluster {0} or service {1} does not exist".format(cluster_name, service_name))
|
raise Exception("cluster {0} or service {1} does not exist".format(cluster_name, service_name))
|
||||||
|
|
||||||
|
def register_container_instance(self, cluster_str, ec2_instance_id):
|
||||||
|
cluster_name = cluster_str.split('/')[-1]
|
||||||
|
if cluster_name not in self.clusters:
|
||||||
|
raise Exception("{0} is not a cluster".format(cluster_name))
|
||||||
|
container_instance = ContainerInstance(ec2_instance_id)
|
||||||
|
if not self.container_instances.get(cluster_name):
|
||||||
|
self.container_instances[cluster_name] = {}
|
||||||
|
container_instance_id = container_instance.containerInstanceArn.split('/')[-1]
|
||||||
|
self.container_instances[cluster_name][container_instance_id] = container_instance
|
||||||
|
return container_instance
|
||||||
|
|
||||||
|
def list_container_instances(self, cluster_str):
|
||||||
|
cluster_name = cluster_str.split('/')[-1]
|
||||||
|
container_instances_values = self.container_instances.get(cluster_name, {}).values()
|
||||||
|
container_instances = [ci.containerInstanceArn for ci in container_instances_values]
|
||||||
|
return sorted(container_instances)
|
||||||
|
|
||||||
|
def describe_container_instances(self, cluster_str, list_container_instance_ids):
|
||||||
|
cluster_name = cluster_str.split('/')[-1]
|
||||||
|
if cluster_name not in self.clusters:
|
||||||
|
raise Exception("{0} is not a cluster".format(cluster_name))
|
||||||
|
failures = []
|
||||||
|
container_instance_objects = []
|
||||||
|
for container_instance_id in list_container_instance_ids:
|
||||||
|
container_instance = self.container_instances[cluster_name].get(container_instance_id, None)
|
||||||
|
if container_instance is not None:
|
||||||
|
container_instance_objects.append(container_instance)
|
||||||
|
else:
|
||||||
|
failures.append(ContainerInstanceFailure('MISSING', container_instance_id))
|
||||||
|
|
||||||
|
return container_instance_objects, failures
|
||||||
|
|
||||||
|
def deregister_container_instance(self, cluster_str, container_instance_str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
ecs_backends = {}
|
ecs_backends = {}
|
||||||
for region, ec2_backend in ec2_backends.items():
|
for region, ec2_backend in ec2_backends.items():
|
||||||
|
@ -113,3 +113,29 @@ class EC2ContainerServiceResponse(BaseResponse):
|
|||||||
return json.dumps({
|
return json.dumps({
|
||||||
'service': service.response_object
|
'service': service.response_object
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def register_container_instance(self):
|
||||||
|
cluster_str = self._get_param('cluster')
|
||||||
|
instance_identity_document_str = self._get_param('instanceIdentityDocument')
|
||||||
|
instance_identity_document = json.loads(instance_identity_document_str)
|
||||||
|
ec2_instance_id = instance_identity_document["instanceId"]
|
||||||
|
container_instance = self.ecs_backend.register_container_instance(cluster_str, ec2_instance_id)
|
||||||
|
return json.dumps({
|
||||||
|
'containerInstance' : container_instance.response_object
|
||||||
|
})
|
||||||
|
|
||||||
|
def list_container_instances(self):
|
||||||
|
cluster_str = self._get_param('cluster')
|
||||||
|
container_instance_arns = self.ecs_backend.list_container_instances(cluster_str)
|
||||||
|
return json.dumps({
|
||||||
|
'containerInstanceArns': container_instance_arns
|
||||||
|
})
|
||||||
|
|
||||||
|
def describe_container_instances(self):
|
||||||
|
cluster_str = self._get_param('cluster')
|
||||||
|
list_container_instance_arns = self._get_param('containerInstances')
|
||||||
|
container_instances, failures = self.ecs_backend.describe_container_instances(cluster_str, list_container_instance_arns)
|
||||||
|
return json.dumps({
|
||||||
|
'failures': [ci.response_object for ci in failures],
|
||||||
|
'containerInstances': [ci.response_object for ci in container_instances]
|
||||||
|
})
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import boto3
|
import boto3
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
import json
|
||||||
|
from moto.ec2 import utils as ec2_utils
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from moto import mock_ecs
|
from moto import mock_ecs
|
||||||
|
from moto import mock_ec2
|
||||||
|
|
||||||
|
|
||||||
@mock_ecs
|
@mock_ecs
|
||||||
@ -334,3 +338,118 @@ def test_delete_service():
|
|||||||
response['service']['serviceName'].should.equal('test_ecs_service')
|
response['service']['serviceName'].should.equal('test_ecs_service')
|
||||||
response['service']['status'].should.equal('ACTIVE')
|
response['service']['status'].should.equal('ACTIVE')
|
||||||
response['service']['taskDefinition'].should.equal('arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1')
|
response['service']['taskDefinition'].should.equal('arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1')
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
@mock_ecs
|
||||||
|
def test_register_container_instance():
|
||||||
|
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'
|
||||||
|
|
||||||
|
_ = 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)
|
||||||
|
)
|
||||||
|
|
||||||
|
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']
|
||||||
|
arn_part = full_arn.split('/')
|
||||||
|
arn_part[0].should.equal('arn:aws:ecs:us-east-1:012345678910:container-instance')
|
||||||
|
arn_part[1].should.equal(str(UUID(arn_part[1])))
|
||||||
|
response['containerInstance']['status'].should.equal('ACTIVE')
|
||||||
|
len(response['containerInstance']['registeredResources']).should.equal(0)
|
||||||
|
len(response['containerInstance']['remainingResources']).should.equal(0)
|
||||||
|
response['containerInstance']['agentConnected'].should.equal(True)
|
||||||
|
response['containerInstance']['versionInfo']['agentVersion'].should.equal('1.0.0')
|
||||||
|
response['containerInstance']['versionInfo']['agentHash'].should.equal('4023248')
|
||||||
|
response['containerInstance']['versionInfo']['dockerVersion'].should.equal('DockerVersion: 1.5.0')
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
@mock_ecs
|
||||||
|
def test_list_container_instances():
|
||||||
|
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'
|
||||||
|
_ = ecs_client.create_cluster(
|
||||||
|
clusterName=test_cluster_name
|
||||||
|
)
|
||||||
|
|
||||||
|
instance_to_create = 3
|
||||||
|
test_instance_arns = []
|
||||||
|
for i in range(0, instance_to_create):
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
response = ecs_client.register_container_instance(
|
||||||
|
cluster=test_cluster_name,
|
||||||
|
instanceIdentityDocument=instance_id_document)
|
||||||
|
|
||||||
|
test_instance_arns.append(response['containerInstance']['containerInstanceArn'])
|
||||||
|
|
||||||
|
response = ecs_client.list_container_instances(cluster=test_cluster_name)
|
||||||
|
|
||||||
|
len(response['containerInstanceArns']).should.equal(instance_to_create)
|
||||||
|
for arn in test_instance_arns:
|
||||||
|
response['containerInstanceArns'].should.contain(arn)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
@mock_ecs
|
||||||
|
def test_describe_container_instances():
|
||||||
|
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'
|
||||||
|
_ = ecs_client.create_cluster(
|
||||||
|
clusterName=test_cluster_name
|
||||||
|
)
|
||||||
|
|
||||||
|
instance_to_create = 3
|
||||||
|
test_instance_arns = []
|
||||||
|
for i in range(0, instance_to_create):
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
response = ecs_client.register_container_instance(
|
||||||
|
cluster=test_cluster_name,
|
||||||
|
instanceIdentityDocument=instance_id_document)
|
||||||
|
|
||||||
|
test_instance_arns.append(response['containerInstance']['containerInstanceArn'])
|
||||||
|
|
||||||
|
test_instance_ids = list(map((lambda x: x.split('/')[1]), test_instance_arns))
|
||||||
|
response = ecs_client.describe_container_instances(cluster=test_cluster_name, containerInstances=test_instance_ids)
|
||||||
|
len(response['failures']).should.equal(0)
|
||||||
|
len(response['containerInstances']).should.equal(instance_to_create)
|
||||||
|
response_arns = [ci['containerInstanceArn'] for ci in response['containerInstances']]
|
||||||
|
for arn in test_instance_arns:
|
||||||
|
response_arns.should.contain(arn)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user