text: from __future__ import unicode_literals
  import uuid
  from datetime import datetime
  from random import random, randint
  import boto3
  
  import pytz
  from moto.core.exceptions import JsonRESTError
  from moto.core import BaseBackend, BaseModel
  from moto.ec2 import ec2_backends
  from copy import copy
  
  
  class BaseObject(BaseModel):
  
      def camelCase(self, key):
          words = []
          for i, word in enumerate(key.split('_')):
              if i > 0:
                  words.append(word.title())
              else:
                  words.append(word)
          return ''.join(words)
  
      def gen_response_object(self):
          response_object = copy(self.__dict__)
          for key, value in self.__dict__.items():
              if '_' in key:
                  response_object[self.camelCase(key)] = value
                  del response_object[key]
          return response_object
  
      @property
      def response_object(self):
          return self.gen_response_object()
  
  
  class Cluster(BaseObject):
  
      def __init__(self, cluster_name):
          self.active_services_count = 0
          self.arn = 'arn:aws:ecs:us-east-1:012345678910:cluster/{0}'.format(
              cluster_name)
          self.name = cluster_name
          self.pending_tasks_count = 0
          self.registered_container_instances_count = 0
          self.running_tasks_count = 0
          self.status = 'ACTIVE'
  
      @property
      def physical_resource_id(self):
          return self.name
  
      @property
      def response_object(self):
          response_object = self.gen_response_object()
          response_object['clusterArn'] = self.arn
          response_object['clusterName'] = self.name
          del response_object['arn'], response_object['name']
          return response_object
  
      @classmethod
      def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
          properties = cloudformation_json['Properties']
  
          ecs_backend = ecs_backends[region_name]
          return ecs_backend.create_cluster(
              # ClusterName is optional in CloudFormation, thus create a random
              # name if necessary
              cluster_name=properties.get(
                  'ClusterName', 'ecscluster{0}'.format(int(random() * 10 ** 6))),
          )
  
      @classmethod
      def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
          properties = cloudformation_json['Properties']
  
          if original_resource.name != properties['ClusterName']:
              ecs_backend = ecs_backends[region_name]
              ecs_backend.delete_cluster(original_resource.arn)
              return ecs_backend.create_cluster(
                  # ClusterName is optional in CloudFormation, thus create a
                  # random name if necessary
                  cluster_name=properties.get(
                      'ClusterName', 'ecscluster{0}'.format(int(random() * 10 ** 6))),
              )
          else:
              # no-op when nothing changed between old and new resources
              return original_resource
  
  
  class TaskDefinition(BaseObject):
  
      def __init__(self, family, revision, container_definitions, volumes=None):
          self.family = family
          self.revision = revision
          self.arn = 'arn:aws:ecs:us-east-1:012345678910:task-definition/{0}:{1}'.format(
              family, revision)
          self.container_definitions = container_definitions
          if volumes is None:
              self.volumes = []
          else:
              self.volumes = volumes
  
      @property
      def response_object(self):
          response_object = self.gen_response_object()
          response_object['taskDefinitionArn'] = response_object['arn']
          del response_object['arn']
          return response_object
  
      @classmethod
      def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
          properties = cloudformation_json['Properties']
  
          family = properties.get(
              'Family', 'task-definition-{0}'.format(int(random() * 10 ** 6)))
          container_definitions = properties['ContainerDefinitions']
          volumes = properties.get('Volumes')
  
          ecs_backend = ecs_backends[region_name]
          return ecs_backend.register_task_definition(
              family=family, container_definitions=container_definitions, volumes=volumes)
  
      @classmethod
      def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
          properties = cloudformation_json['Properties']
  
          family = properties.get(
              'Family', 'task-definition-{0}'.format(int(random() * 10 ** 6)))
          container_definitions = properties['ContainerDefinitions']
          volumes = properties.get('Volumes')
          if (original_resource.family != family or
                  original_resource.container_definitions != container_definitions or
                  original_resource.volumes != volumes):
                  # currently TaskRoleArn isn't stored at TaskDefinition
                  # instances
              ecs_backend = ecs_backends[region_name]
              ecs_backend.deregister_task_definition(original_resource.arn)
              return ecs_backend.register_task_definition(
                  family=family, container_definitions=container_definitions, volumes=volumes)
          else:
              # no-op when nothing changed between old and new resources
              return original_resource
  
  
  class Task(BaseObject):
  
      def __init__(self, cluster, task_definition, container_instance_arn,
                   resource_requirements, overrides={}, started_by=''):
          self.cluster_arn = cluster.arn
          self.task_arn = 'arn:aws:ecs:us-east-1:012345678910:task/{0}'.format(
              str(uuid.uuid4()))
          self.container_instance_arn = container_instance_arn
          self.last_status = 'RUNNING'
          self.desired_status = 'RUNNING'
          self.task_definition_arn = task_definition.arn
          self.overrides = overrides
          self.containers = []
          self.started_by = started_by
          self.stopped_reason = ''
          self.resource_requirements = resource_requirements
  
      @property
      def response_object(self):
          response_object = self.gen_response_object()
          return response_object
  
  
  class Service(BaseObject):
  
      def __init__(self, cluster, service_name, task_definition, desired_count):
          self.cluster_arn = cluster.arn
          self.arn = 'arn:aws:ecs:us-east-1:012345678910:service/{0}'.format(
              service_name)
          self.name = service_name
          self.status = 'ACTIVE'
          self.running_count = 0
          self.task_definition = task_definition.arn
          self.desired_count = desired_count
          self.events = []
          self.deployments = [
              {
                  'createdAt': datetime.now(pytz.utc),
                  'desiredCount': self.desired_count,
                  'id': 'ecs-svc/{}'.format(randint(0, 32**12)),
                  'pendingCount': self.desired_count,
                  'runningCount': 0,
                  'status': 'PRIMARY',
                  'taskDefinition': task_definition.arn,
                  'updatedAt': datetime.now(pytz.utc),
              }
          ]
          self.load_balancers = []
          self.pending_count = 0
  
      @property
      def physical_resource_id(self):
          return self.arn
  
      @property
      def response_object(self):
          response_object = self.gen_response_object()
          del response_object['name'], response_object['arn']
          response_object['serviceName'] = self.name
          response_object['serviceArn'] = self.arn
  
          for deployment in response_object['deployments']:
              if isinstance(deployment['createdAt'], datetime):
                  deployment['createdAt'] = deployment['createdAt'].isoformat()
              if isinstance(deployment['updatedAt'], datetime):
                  deployment['updatedAt'] = deployment['updatedAt'].isoformat()
  
          return response_object
  
      @classmethod
      def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
          properties = cloudformation_json['Properties']
          if isinstance(properties['Cluster'], Cluster):
              cluster = properties['Cluster'].name
          else:
              cluster = properties['Cluster']
          if isinstance(properties['TaskDefinition'], TaskDefinition):
              task_definition = properties['TaskDefinition'].family
          else:
              task_definition = properties['TaskDefinition']
          service_name = '{0}Service{1}'.format(cluster, int(random() * 10 ** 6))
          desired_count = properties['DesiredCount']
          # TODO: LoadBalancers
          # TODO: Role
  
          ecs_backend = ecs_backends[region_name]
          return ecs_backend.create_service(
              cluster, service_name, task_definition, desired_count)
  
      @classmethod
      def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
          properties = cloudformation_json['Properties']
          if isinstance(properties['Cluster'], Cluster):
              cluster_name = properties['Cluster'].name
          else:
              cluster_name = properties['Cluster']
          if isinstance(properties['TaskDefinition'], TaskDefinition):
              task_definition = properties['TaskDefinition'].family
          else:
              task_definition = properties['TaskDefinition']
          desired_count = properties['DesiredCount']
  
          ecs_backend = ecs_backends[region_name]
          service_name = original_resource.name
          if original_resource.cluster_arn != Cluster(cluster_name).arn:
              # TODO: LoadBalancers
              # TODO: Role
              ecs_backend.delete_service(cluster_name, service_name)
              new_service_name = '{0}Service{1}'.format(
                  cluster_name, int(random() * 10 ** 6))
              return ecs_backend.create_service(
                  cluster_name, new_service_name, task_definition, desired_count)
          else:
              return ecs_backend.update_service(cluster_name, service_name, task_definition, desired_count)
  
  
  class ContainerInstance(BaseObject):
  
      def __init__(self, ec2_instance_id, region_name):
          self.ec2_instance_id = ec2_instance_id
          self.agent_connected = True
          self.status = 'ACTIVE'
          self.registered_resources = [
              {'doubleValue': 0.0,
               'integerValue': 4096,
               'longValue': 0,
               'name': 'CPU',
               'type': 'INTEGER'},
              {'doubleValue': 0.0,
               'integerValue': 7482,
               'longValue': 0,
               'name': 'MEMORY',
               'type': 'INTEGER'},
              {'doubleValue': 0.0,
               'integerValue': 0,
               'longValue': 0,
               'name': 'PORTS',
               'stringSetValue': ['22', '2376', '2375', '51678', '51679'],
               'type': 'STRINGSET'},
              {'doubleValue': 0.0,
               'integerValue': 0,
               'longValue': 0,
               'name': 'PORTS_UDP',
               'stringSetValue': [],
               'type': 'STRINGSET'}]
          self.container_instance_arn = "arn:aws:ecs:us-east-1:012345678910:container-instance/{0}".format(
              str(uuid.uuid4()))
          self.pending_tasks_count = 0
          self.remaining_resources = [
              {'doubleValue': 0.0,
               'integerValue': 4096,
               'longValue': 0,
               'name': 'CPU',
               'type': 'INTEGER'},
              {'doubleValue': 0.0,
               'integerValue': 7482,
               'longValue': 0,
               'name': 'MEMORY',
               'type': 'INTEGER'},
              {'doubleValue': 0.0,
               'integerValue': 0,
               'longValue': 0,
               'name': 'PORTS',
               'stringSetValue': ['22', '2376', '2375', '51678', '51679'],
               'type': 'STRINGSET'},
              {'doubleValue': 0.0,
               'integerValue': 0,
               'longValue': 0,
               'name': 'PORTS_UDP',
               'stringSetValue': [],
               'type': 'STRINGSET'}
          ]
          self.running_tasks_count = 0
          self.version_info = {
              'agentVersion': "1.0.0",
              'agentHash': '4023248',
              'dockerVersion': 'DockerVersion: 1.5.0'
          }
          ec2_backend = ec2_backends[region_name]
          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
      def response_object(self):
          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
  
      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):
  
      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):
  
      def __init__(self, region_name):
          super(EC2ContainerServiceBackend, self).__init__()
          self.clusters = {}
          self.task_definitions = {}
          self.tasks = {}
          self.services = {}
          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):
          task_definition_name = task_definition_str.split('/')[-1]
          if ':' in task_definition_name:
              family, revision = task_definition_name.split(':')
              revision = int(revision)
          else:
              family = task_definition_name
              revision = len(self.task_definitions.get(family, []))
  
          if family in self.task_definitions and 0 < revision <= len(self.task_definitions[family]):
              return self.task_definitions[family][revision - 1]
          elif family in self.task_definitions and revision == -1:
              return self.task_definitions[family][revision]
          else:
              raise Exception(
                  "{0} is not a task_definition".format(task_definition_name))
  
      def create_cluster(self, cluster_name):
          cluster = Cluster(cluster_name)
          self.clusters[cluster_name] = cluster
          return cluster
  
      def list_clusters(self):
          """
          maxSize and pagination not implemented
          """
          return [cluster.arn for cluster in self.clusters.values()]
  
      def describe_clusters(self, list_clusters_name=None):
          list_clusters = []
          if list_clusters_name is None:
              if 'default' in self.clusters:
                  list_clusters.append(self.clusters['default'].response_object)
          else:
              for cluster in list_clusters_name:
                  cluster_name = cluster.split('/')[-1]
                  if cluster_name in self.clusters:
                      list_clusters.append(
                          self.clusters[cluster_name].response_object)
                  else:
                      raise Exception(
                          "{0} is not a cluster".format(cluster_name))
          return list_clusters
  
      def delete_cluster(self, cluster_str):
          cluster_name = cluster_str.split('/')[-1]
          if cluster_name in self.clusters:
              return self.clusters.pop(cluster_name)
          else:
              raise Exception("{0} is not a cluster".format(cluster_name))
  
      def register_task_definition(self, family, container_definitions, volumes):
          if family in self.task_definitions:
              revision = len(self.task_definitions[family]) + 1
          else:
              self.task_definitions[family] = []
              revision = 1
          task_definition = TaskDefinition(
              family, revision, container_definitions, volumes)
          self.task_definitions[family].append(task_definition)
  
          return task_definition
  
      def list_task_definitions(self):
          """
          Filtering not implemented
          """
          task_arns = []
          for task_definition_list in self.task_definitions.values():
              task_arns.extend(
                  [task_definition.arn for task_definition in task_definition_list])
          return task_arns
  
      def deregister_task_definition(self, task_definition_str):
          task_definition_name = task_definition_str.split('/')[-1]
          family, revision = task_definition_name.split(':')
          revision = int(revision)
          if family in self.task_definitions and 0 < revision <= len(self.task_definitions[family]):
              return self.task_definitions[family].pop(revision - 1)
          else:
              raise Exception(
                  "{0} is not a task_definition".format(task_definition_name))
  
      def run_task(self, cluster_str, task_definition_str, count, overrides, started_by):
          cluster_name = cluster_str.split('/')[-1]
          if cluster_name in self.clusters:
              cluster = self.clusters[cluster_name]
          else:
              raise Exception("{0} is not a cluster".format(cluster_name))
          task_definition = self.describe_task_definition(task_definition_str)
          if cluster_name not in self.tasks:
              self.tasks[cluster_name] = {}
          tasks = []
          container_instances = list(
              self.container_instances.get(cluster_name, {}).keys())
          if not container_instances:
              raise Exception("No instances found in cluster {}".format(cluster_name))
          active_container_instances = [x for x in container_instances if
                                        self.container_instances[cluster_name][x].status == 'ACTIVE']
          resource_requirements = self._calculate_task_resource_requirements(task_definition)
          # TODO: return event about unable to place task if not able to place enough tasks to meet count
          placed_count = 0
          for container_instance in active_container_instances:
              container_instance = self.container_instances[cluster_name][container_instance]
              container_instance_arn = container_instance.container_instance_arn
              try_to_place = True
              while try_to_place:
                  can_be_placed, message = self._can_be_placed(container_instance, resource_requirements)
                  if can_be_placed:
                      task = Task(cluster, task_definition, container_instance_arn,
                                  resource_requirements, overrides or {}, started_by or '')
                      self.update_container_instance_resources(container_instance, resource_requirements)
                      tasks.append(task)
                      self.tasks[cluster_name][task.task_arn] = task
                      placed_count += 1
                      if placed_count == count:
                          return tasks
                  else:
                      try_to_place = False
          return tasks
  
      @staticmethod
      def _calculate_task_resource_requirements(task_definition):
          resource_requirements = {"CPU": 0, "MEMORY": 0, "PORTS": [], "PORTS_UDP": []}
          for container_definition in task_definition.container_definitions:
              resource_requirements["CPU"] += container_definition.get('cpu')
              resource_requirements["MEMORY"] += container_definition.get("memory")
              for port_mapping in container_definition.get("portMappings", []):
                  resource_requirements["PORTS"].append(port_mapping.get('hostPort'))
          return resource_requirements
  
      @staticmethod
      def _can_be_placed(container_instance, task_resource_requirements):
          """
  
          :param container_instance: The container instance trying to be placed onto
          :param task_resource_requirements: The calculated resource requirements of the task in the form of a dict
          :return: A boolean stating whether the given container instance has enough resources to have the task placed on
          it as well as a description, if it cannot be placed this will describe why.
          """
          # TODO: Implement default and other placement strategies as well as constraints:
          # docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement.html
          remaining_cpu = 0
          remaining_memory = 0
          reserved_ports = []
          for resource in container_instance.remaining_resources:
              if resource.get("name") == "CPU":
                  remaining_cpu = resource.get("integerValue")
              elif resource.get("name") == "MEMORY":
                  remaining_memory = resource.get("integerValue")
              elif resource.get("name") == "PORTS":
                  reserved_ports = resource.get("stringSetValue")
          if task_resource_requirements.get("CPU") > remaining_cpu:
              return False, "Not enough CPU credits"
          if task_resource_requirements.get("MEMORY") > remaining_memory:
              return False, "Not enough memory"
          ports_needed = task_resource_requirements.get("PORTS")
          for port in ports_needed:
              if str(port) in reserved_ports:
                  return False, "Port clash"
          return True, "Can be placed"
  
      def start_task(self, cluster_str, task_definition_str, container_instances, overrides, started_by):
          cluster_name = cluster_str.split('/')[-1]
          if cluster_name in self.clusters:
              cluster = self.clusters[cluster_name]
          else:
              raise Exception("{0} is not a cluster".format(cluster_name))
          task_definition = self.describe_task_definition(task_definition_str)
          if cluster_name not in self.tasks:
              self.tasks[cluster_name] = {}
          tasks = []
          if not container_instances:
              raise Exception("No container instance list provided")
  
          container_instance_ids = [x.split('/')[-1]
                                    for x in container_instances]
          resource_requirements = self._calculate_task_resource_requirements(task_definition)
          for container_instance_id in container_instance_ids:
              container_instance = self.container_instances[cluster_name][
                  container_instance_id
              ]
              task = Task(cluster, task_definition, container_instance.container_instance_arn,
                          resource_requirements, overrides or {}, started_by or '')
              tasks.append(task)
              self.update_container_instance_resources(container_instance, resource_requirements)
              self.tasks[cluster_name][task.task_arn] = task
          return tasks
  
      def describe_tasks(self, cluster_str, tasks):
          cluster_name = cluster_str.split('/')[-1]
          if cluster_name in self.clusters:
              cluster = self.clusters[cluster_name]
          else:
              raise Exception("{0} is not a cluster".format(cluster_name))
          if not tasks:
              raise Exception("tasks cannot be empty")
          response = []
          for cluster, cluster_tasks in self.tasks.items():
              for task_id, task in cluster_tasks.items():
                  if task_id in tasks or task.task_arn in tasks:
                      response.append(task)
          return response
  
      def list_tasks(self, cluster_str, container_instance, family, started_by, service_name, desiredStatus):
          filtered_tasks = []
          for cluster, tasks in self.tasks.items():
              for arn, task in tasks.items():
                  filtered_tasks.append(task)
          if cluster_str:
              cluster_name = cluster_str.split('/')[-1]
              if cluster_name not in self.clusters:
                  raise Exception("{0} is not a cluster".format(cluster_name))
              filtered_tasks = list(
                  filter(lambda t: cluster_name in t.cluster_arn, filtered_tasks))
  
          if container_instance:
              filtered_tasks = list(filter(
                  lambda t: container_instance in t.container_instance_arn, filtered_tasks))
  
          if started_by:
              filtered_tasks = list(
                  filter(lambda t: started_by == t.started_by, filtered_tasks))
          return [t.task_arn for t in filtered_tasks]
  
      def stop_task(self, cluster_str, task_str, reason):
          cluster_name = cluster_str.split('/')[-1]
          if cluster_name not in self.clusters:
              raise Exception("{0} is not a cluster".format(cluster_name))
  
          if not task_str:
              raise Exception("A task ID or ARN is required")
          task_id = task_str.split('/')[-1]
          tasks = self.tasks.get(cluster_name, None)
          if not tasks:
              raise Exception(
                  "Cluster {} has no registered tasks".format(cluster_name))
          for task in tasks.keys():
              if task.endswith(task_id):
                  container_instance_arn = tasks[task].container_instance_arn
                  container_instance = self.container_instances[cluster_name][container_instance_arn.split('/')[-1]]
                  self.update_container_instance_resources(container_instance, tasks[task].resource_requirements,
                                                           removing=True)
                  tasks[task].last_status = 'STOPPED'
                  tasks[task].desired_status = 'STOPPED'
                  tasks[task].stopped_reason = reason
                  return tasks[task]
          raise Exception("Could not find task {} on cluster {}".format(
              task_str, cluster_name))
  
      def create_service(self, cluster_str, service_name, task_definition_str, desired_count):
          cluster_name = cluster_str.split('/')[-1]
          if cluster_name in self.clusters:
              cluster = self.clusters[cluster_name]
          else:
              raise Exception("{0} is not a cluster".format(cluster_name))
          task_definition = self.describe_task_definition(task_definition_str)
          desired_count = desired_count if desired_count is not None else 0
          service = Service(cluster, service_name,
                            task_definition, desired_count)
          cluster_service_pair = '{0}:{1}'.format(cluster_name, service_name)
          self.services[cluster_service_pair] = service
          return service
  
      def list_services(self, cluster_str):
          cluster_name = cluster_str.split('/')[-1]
          service_arns = []
          for key, value in self.services.items():
              if cluster_name + ':' in key:
                  service_arns.append(self.services[key].arn)
          return sorted(service_arns)
  
      def describe_services(self, cluster_str, service_names_or_arns):
          cluster_name = cluster_str.split('/')[-1]
          result = []
          for existing_service_name, existing_service_obj in sorted(self.services.items()):
              for requested_name_or_arn in service_names_or_arns:
                  cluster_service_pair = '{0}:{1}'.format(
                      cluster_name, requested_name_or_arn)
                  if cluster_service_pair == existing_service_name or existing_service_obj.arn == requested_name_or_arn:
                      result.append(existing_service_obj)
          return result
  
      def update_service(self, cluster_str, service_name, task_definition_str, desired_count):
          cluster_name = cluster_str.split('/')[-1]
          cluster_service_pair = '{0}:{1}'.format(cluster_name, service_name)
          if cluster_service_pair in self.services:
              if task_definition_str is not None:
                  self.describe_task_definition(task_definition_str)
                  self.services[
                      cluster_service_pair].task_definition = task_definition_str
              if desired_count is not None:
                  self.services[
                      cluster_service_pair].desired_count = desired_count
              return self.services[cluster_service_pair]
          else:
              raise Exception("cluster {0} or service {1} does not exist".format(
                  cluster_name, service_name))
  
      def delete_service(self, cluster_name, service_name):
          cluster_service_pair = '{0}:{1}'.format(cluster_name, service_name)
          if cluster_service_pair in self.services:
              service = self.services[cluster_service_pair]
              if service.desired_count > 0:
                  raise Exception("Service must have desiredCount=0")
              else:
                  return self.services.pop(cluster_service_pair)
          else:
              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, self.region_name)
          if not self.container_instances.get(cluster_name):
              self.container_instances[cluster_name] = {}
          container_instance_id = container_instance.container_instance_arn.split(
              '/')[-1]
          self.container_instances[cluster_name][
              container_instance_id] = container_instance
          self.clusters[cluster_name].registered_container_instances_count += 1
          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.container_instance_arn 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_id = container_instance_id.split('/')[-1]
              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 update_container_instances_state(self, cluster_str, list_container_instance_ids, status):
          cluster_name = cluster_str.split('/')[-1]
          if cluster_name not in self.clusters:
              raise Exception("{0} is not a cluster".format(cluster_name))
          status = status.upper()
          if status not in ['ACTIVE', 'DRAINING']:
              raise Exception("An error occurred (InvalidParameterException) when calling the UpdateContainerInstancesState operation: \
                              Container instances status should be one of [ACTIVE,DRAINING]")
          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.status = status
                  container_instance_objects.append(container_instance)
              else:
                  failures.append(ContainerInstanceFailure('MISSING', container_instance_id))
  
          return container_instance_objects, failures
  
      def update_container_instance_resources(self, container_instance, task_resources, removing=False):
          resource_multiplier = 1
          if removing:
              resource_multiplier = -1
          for resource in container_instance.remaining_resources:
              if resource.get("name") == "CPU":
                  resource["integerValue"] -= task_resources.get('CPU') * resource_multiplier
              elif resource.get("name") == "MEMORY":
                  resource["integerValue"] -= task_resources.get('MEMORY') * resource_multiplier
              elif resource.get("name") == "PORTS":
                  for port in task_resources.get("PORTS"):
                      if removing:
                          resource["stringSetValue"].remove(str(port))
                      else:
                          resource["stringSetValue"].append(str(port))
          container_instance.running_tasks_count += resource_multiplier * 1
  
      def deregister_container_instance(self, cluster_str, container_instance_str, force):
          failures = []
          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_id = container_instance_str.split('/')[-1]
          container_instance = self.container_instances[cluster_name].get(container_instance_id)
          if container_instance is None:
              raise Exception("{0} is not a container id in the cluster")
          if not force and container_instance.running_tasks_count > 0:
              raise Exception("Found running tasks on the instance.")
          # Currently assume that people might want to do something based around deregistered instances
          # with tasks left running on them - but nothing if no tasks were running already
          elif force and container_instance.running_tasks_count > 0:
              if not self.container_instances.get('orphaned'):
                  self.container_instances['orphaned'] = {}
              self.container_instances['orphaned'][container_instance_id] = container_instance
          del(self.container_instances[cluster_name][container_instance_id])
          self._respond_to_cluster_state_update(cluster_str)
          return container_instance, failures
  
      def _respond_to_cluster_state_update(self, cluster_str):
          cluster_name = cluster_str.split('/')[-1]
          if cluster_name not in self.clusters:
              raise Exception("{0} is not a cluster".format(cluster_name))
          pass
  
      def put_attributes(self, cluster_name, attributes=None):
          if cluster_name is None or cluster_name not in self.clusters:
              raise JsonRESTError('ClusterNotFoundException', 'Cluster not found', status=400)
  
          if attributes is None:
              raise JsonRESTError('InvalidParameterException', 'attributes value is required')
  
          for attr in attributes:
              self._put_attribute(cluster_name, attr['name'], attr.get('value'), attr.get('targetId'), attr.get('targetType'))
  
      def _put_attribute(self, cluster_name, name, value=None, target_id=None, target_type=None):
          if target_id is None and target_type is None:
              for instance in self.container_instances[cluster_name].values():
                  instance.attributes[name] = value
          elif target_type is None:
              # targetId is full container instance arn
              try:
                  arn = target_id.rsplit('/', 1)[-1]
                  self.container_instances[cluster_name][arn].attributes[name] = value
              except KeyError:
                  raise JsonRESTError('TargetNotFoundException', 'Could not find {0}'.format(target_id))
          else:
              # targetId is container uuid, targetType must be container-instance
              try:
                  if target_type != 'container-instance':
                      raise JsonRESTError('TargetNotFoundException', 'Could not find {0}'.format(target_id))
  
                  self.container_instances[cluster_name][target_id].attributes[name] = value
              except KeyError:
                  raise JsonRESTError('TargetNotFoundException', 'Could not find {0}'.format(target_id))
  
      def list_attributes(self, target_type, cluster_name=None, attr_name=None, attr_value=None, max_results=None, next_token=None):
          if target_type != 'container-instance':
              raise JsonRESTError('InvalidParameterException', 'targetType must be container-instance')
  
          filters = [lambda x: True]
  
          # item will be {0 cluster_name, 1 arn, 2 name, 3 value}
          if cluster_name is not None:
              filters.append(lambda item: item[0] == cluster_name)
          if attr_name:
              filters.append(lambda item: item[2] == attr_name)
          if attr_name:
              filters.append(lambda item: item[3] == attr_value)
  
          all_attrs = []
          for cluster_name, cobj in self.container_instances.items():
              for container_instance in cobj.values():
                  for key, value in container_instance.attributes.items():
                      all_attrs.append((cluster_name, container_instance.container_instance_arn, key, value))
  
          return filter(lambda x: all(f(x) for f in filters), all_attrs)
  
      def delete_attributes(self, cluster_name, attributes=None):
          if cluster_name is None or cluster_name not in self.clusters:
              raise JsonRESTError('ClusterNotFoundException', 'Cluster not found', status=400)
  
          if attributes is None:
              raise JsonRESTError('InvalidParameterException', 'attributes value is required')
  
          for attr in attributes:
              self._delete_attribute(cluster_name, attr['name'], attr.get('value'), attr.get('targetId'), attr.get('targetType'))
  
      def _delete_attribute(self, cluster_name, name, value=None, target_id=None, target_type=None):
          if target_id is None and target_type is None:
              for instance in self.container_instances[cluster_name].values():
                  if name in instance.attributes and instance.attributes[name] == value:
                      del instance.attributes[name]
          elif target_type is None:
              # targetId is full container instance arn
              try:
                  arn = target_id.rsplit('/', 1)[-1]
                  instance = self.container_instances[cluster_name][arn]
                  if name in instance.attributes and instance.attributes[name] == value:
                      del instance.attributes[name]
              except KeyError:
                  raise JsonRESTError('TargetNotFoundException', 'Could not find {0}'.format(target_id))
          else:
              # targetId is container uuid, targetType must be container-instance
              try:
                  if target_type != 'container-instance':
                      raise JsonRESTError('TargetNotFoundException', 'Could not find {0}'.format(target_id))
  
                  instance = self.container_instances[cluster_name][target_id]
                  if name in instance.attributes and instance.attributes[name] == value:
                      del instance.attributes[name]
              except KeyError:
                  raise JsonRESTError('TargetNotFoundException', 'Could not find {0}'.format(target_id))
  
      def list_task_definition_families(self, family_prefix=None, status=None, max_results=None, next_token=None):
          for task_fam in self.task_definitions:
              if family_prefix is not None and not task_fam.startswith(family_prefix):
                  continue
  
              yield task_fam
  
  
  available_regions = boto3.session.Session().get_available_regions("ecs")
  ecs_backends = {region: EC2ContainerServiceBackend(region) for region in available_regions}
  