agent/engine/dockerstate/docker_task_engine_state.go (500 lines of code) (raw):

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. package dockerstate import ( "encoding/json" "strings" "sync" apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container" apitask "github.com/aws/amazon-ecs-agent/agent/api/task" "github.com/aws/amazon-ecs-agent/agent/engine/image" apiresource "github.com/aws/amazon-ecs-agent/ecs-agent/api/attachment/resource" ni "github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/networkinterface" "github.com/cihub/seelog" ) // TaskEngineState keeps track of all mappings between tasks we know about // and containers docker runs type TaskEngineState interface { // AllTasks returns all of the tasks AllTasks() []*apitask.Task // AllExternalTasks returns all tasks with IsInternal==false (i.e. customer-initiated tasks). // Currently, ServiceConnect AppNet Relay task is the only internal task. AllExternalTasks() []*apitask.Task // AllENIAttachments returns all of the eni attachments AllENIAttachments() []*ni.ENIAttachment // AllImageStates returns all of the image.ImageStates AllImageStates() []*image.ImageState // GetAllContainerIDs returns all of the Container Ids GetAllContainerIDs() []string // ContainerByID returns an apicontainer.DockerContainer for a given container ID ContainerByID(id string) (*apicontainer.DockerContainer, bool) // ContainerMapByArn returns a map of containers belonging to a particular task ARN ContainerMapByArn(arn string) (map[string]*apicontainer.DockerContainer, bool) // PulledContainerMapByArn returns a map of pulled containers belonging to a particular task ARN PulledContainerMapByArn(arn string) (map[string]*apicontainer.DockerContainer, bool) // TaskByShortID retrieves the task of a given docker short container id TaskByShortID(cid string) ([]*apitask.Task, bool) // TaskByID returns an apitask.Task for a given container ID TaskByID(cid string) (*apitask.Task, bool) // TaskByArn returns a task for a given ARN TaskByArn(arn string) (*apitask.Task, bool) // AddTask adds a task to the state to be stored AddTask(task *apitask.Task) // AddPulledContainer adds a pulled container to the state to be stored for a given task AddPulledContainer(container *apicontainer.DockerContainer, task *apitask.Task) // AddContainer adds a container to the state to be stored for a given task AddContainer(container *apicontainer.DockerContainer, task *apitask.Task) // AddImageState adds an image.ImageState to be stored AddImageState(imageState *image.ImageState) // AddENIAttachment adds an eni attachment from acs to be stored AddENIAttachment(eni *ni.ENIAttachment) // RemoveENIAttachment removes an eni attachment to stop tracking RemoveENIAttachment(mac string) // ENIByMac returns the specific ENIAttachment of the given mac address ENIByMac(mac string) (*ni.ENIAttachment, bool) // RemoveTask removes a task from the state RemoveTask(task *apitask.Task) // Reset resets all the fileds in the state Reset() // RemoveImageState removes an image.ImageState RemoveImageState(imageState *image.ImageState) // AddTaskIPAddress adds ip adddress for a task arn into the state AddTaskIPAddress(addr string, taskARN string) // GetTaskByIPAddress gets the task arn for an IP address GetTaskByIPAddress(addr string) (string, bool) // GetIPAddressByTaskARN gets the local ip address of a task. GetIPAddressByTaskARN(taskARN string) (string, bool) // DockerIDByV3EndpointID returns a docker ID for a given v3 endpoint ID DockerIDByV3EndpointID(v3EndpointID string) (string, bool) // TaskARNByV3EndpointID returns a taskARN for a given v3 endpoint ID TaskARNByV3EndpointID(v3EndpointID string) (string, bool) // GetAllEBSAttachments returns all of the ebs attachments GetAllEBSAttachments() []*apiresource.ResourceAttachment // AllPendingEBSAttachments reutrns all of the ebs attachments that haven't sent a state change GetAllPendingEBSAttachments() []*apiresource.ResourceAttachment // GetAllPendingEBSAttachmentWithKey returns a map of all pending ebs attachments along with the corresponding volume ID as the key GetAllPendingEBSAttachmentsWithKey() map[string]*apiresource.ResourceAttachment // AddEBSAttachment adds an ebs attachment from acs to be stored AddEBSAttachment(ebs *apiresource.ResourceAttachment) // RemoveEBSAttachment removes an ebs attachment to stop tracking RemoveEBSAttachment(volumeId string) // EBSByVolumeId returns the specific EBSAttachment of the given volume ID GetEBSByVolumeId(volumeId string) (*apiresource.ResourceAttachment, bool) json.Marshaler json.Unmarshaler } // DockerTaskEngineState keeps track of all mappings between tasks we know about // and containers docker runs // It contains a mutex that can be used to ensure out-of-date state cannot be // accessed before an update comes and to ensure multiple goroutines can safely // work with it. // // The methods on it will acquire the read lock, but not all acquire the write // lock (sometimes it is up to the caller). This is because the write lock for // containers should encapsulate the creation of the resource as well as adding, // and creating the resource (docker container) is outside the scope of this // package. This isn't ideal usage and I'm open to this being reworked/improved. // // Some information is duplicated in the interest of having efficient lookups type DockerTaskEngineState struct { lock sync.RWMutex tasks map[string]*apitask.Task // taskarn -> apitask.Task idToTask map[string]string // DockerId -> taskarn taskToID map[string]map[string]*apicontainer.DockerContainer // taskarn -> (containername -> c.DockerContainer) taskToPulledContainer map[string]map[string]*apicontainer.DockerContainer // taskarn -> (containername -> c.DockerContainer) idToContainer map[string]*apicontainer.DockerContainer // DockerId -> c.DockerContainer ebsAttachments map[string]*apiresource.ResourceAttachment // VolumeID -> apiresource.ResourceAttachment eniAttachments map[string]*ni.ENIAttachment // ENIMac -> ni.ENIAttachment imageStates map[string]*image.ImageState ipToTask map[string]string // ip address -> task arn v3EndpointIDToTask map[string]string // container's v3 endpoint id -> taskarn v3EndpointIDToDockerID map[string]string // container's v3 endpoint id -> DockerId } // NewTaskEngineState returns a new TaskEngineState func NewTaskEngineState() TaskEngineState { return newDockerTaskEngineState() } func newDockerTaskEngineState() *DockerTaskEngineState { state := &DockerTaskEngineState{} state.initializeDockerTaskEngineState() return state } func (state *DockerTaskEngineState) initializeDockerTaskEngineState() { state.lock.Lock() defer state.lock.Unlock() state.tasks = make(map[string]*apitask.Task) state.idToTask = make(map[string]string) state.taskToID = make(map[string]map[string]*apicontainer.DockerContainer) state.taskToPulledContainer = make(map[string]map[string]*apicontainer.DockerContainer) state.idToContainer = make(map[string]*apicontainer.DockerContainer) state.imageStates = make(map[string]*image.ImageState) state.ebsAttachments = make(map[string]*apiresource.ResourceAttachment) state.eniAttachments = make(map[string]*ni.ENIAttachment) state.ipToTask = make(map[string]string) state.v3EndpointIDToTask = make(map[string]string) state.v3EndpointIDToDockerID = make(map[string]string) } // Reset resets all the states func (state *DockerTaskEngineState) Reset() { state.initializeDockerTaskEngineState() } // AllTasks returns all of the tasks func (state *DockerTaskEngineState) AllTasks() []*apitask.Task { state.lock.RLock() defer state.lock.RUnlock() return state.allTasksUnsafe() } func (state *DockerTaskEngineState) allTasksUnsafe() []*apitask.Task { return state.getFilteredTasksUnsafe(false) } // AllExternalTasks returns all tasks with IsInternal==false (i.e. all customer-initiated tasks) func (state *DockerTaskEngineState) AllExternalTasks() []*apitask.Task { state.lock.RLock() defer state.lock.RUnlock() return state.allExternalTasksUnsafe() } func (state *DockerTaskEngineState) allExternalTasksUnsafe() []*apitask.Task { return state.getFilteredTasksUnsafe(true) } func (state *DockerTaskEngineState) getFilteredTasksUnsafe(excludeInternal bool) []*apitask.Task { ret := make([]*apitask.Task, len(state.tasks)) ndx := 0 for _, task := range state.tasks { if excludeInternal && task.IsInternal { continue } ret[ndx] = task ndx++ } return ret[:ndx] } // AllImageStates returns all of the image.ImageStates func (state *DockerTaskEngineState) AllImageStates() []*image.ImageState { state.lock.RLock() defer state.lock.RUnlock() return state.allImageStatesUnsafe() } func (state *DockerTaskEngineState) allImageStatesUnsafe() []*image.ImageState { var allImageStates []*image.ImageState for _, imageState := range state.imageStates { allImageStates = append(allImageStates, imageState) } return allImageStates } // AllENIAttachments returns all the enis managed by ecs on the instance func (state *DockerTaskEngineState) AllENIAttachments() []*ni.ENIAttachment { state.lock.RLock() defer state.lock.RUnlock() return state.allENIAttachmentsUnsafe() } func (state *DockerTaskEngineState) allENIAttachmentsUnsafe() []*ni.ENIAttachment { var allENIAttachments []*ni.ENIAttachment for _, v := range state.eniAttachments { allENIAttachments = append(allENIAttachments, v) } return allENIAttachments } // ENIByMac returns the eni object that match the give mac address func (state *DockerTaskEngineState) ENIByMac(mac string) (*ni.ENIAttachment, bool) { state.lock.RLock() defer state.lock.RUnlock() eni, ok := state.eniAttachments[mac] return eni, ok } // AddENIAttachment adds the eni into the state func (state *DockerTaskEngineState) AddENIAttachment(eniAttachment *ni.ENIAttachment) { if eniAttachment == nil { seelog.Debug("Cannot add empty eni attachment information") return } state.lock.Lock() defer state.lock.Unlock() if _, ok := state.eniAttachments[eniAttachment.MACAddress]; !ok { state.eniAttachments[eniAttachment.MACAddress] = eniAttachment } else { seelog.Debugf("Duplicate eni attachment information: %v", eniAttachment) } } // RemoveENIAttachment removes the eni from state and stop managing func (state *DockerTaskEngineState) RemoveENIAttachment(mac string) { if mac == "" { seelog.Debug("Cannot remove empty eni attachment information") return } state.lock.Lock() defer state.lock.Unlock() if _, ok := state.eniAttachments[mac]; ok { delete(state.eniAttachments, mac) } else { seelog.Debugf("Delete non-existed eni attachment: %v", mac) } } // GetAllEBSAttachments returns all the ebs volumes managed by ecs on the instance func (state *DockerTaskEngineState) GetAllEBSAttachments() []*apiresource.ResourceAttachment { state.lock.RLock() defer state.lock.RUnlock() return state.allEBSAttachmentsUnsafe() } func (state *DockerTaskEngineState) allEBSAttachmentsUnsafe() []*apiresource.ResourceAttachment { var allEBSAttachments []*apiresource.ResourceAttachment for _, v := range state.ebsAttachments { allEBSAttachments = append(allEBSAttachments, v) } return allEBSAttachments } // GetAllPendingEBSAttachments returns all the ebs volumes managed by ecs on the instance that haven't been found attached on the host func (state *DockerTaskEngineState) GetAllPendingEBSAttachments() []*apiresource.ResourceAttachment { state.lock.RLock() defer state.lock.RUnlock() return state.allPendingEBSAttachmentsUnsafe() } func (state *DockerTaskEngineState) allPendingEBSAttachmentsUnsafe() []*apiresource.ResourceAttachment { var pendingEBSAttachments []*apiresource.ResourceAttachment for _, v := range state.ebsAttachments { if !v.IsAttached() || !v.IsSent() { pendingEBSAttachments = append(pendingEBSAttachments, v) } } return pendingEBSAttachments } // GetAllPendingEBSAttachmentsWithKey returns all the ebs volumes managed by ecs on the instance that haven't been found attached on the host as a map // with the corresponding volume ID as the key func (state *DockerTaskEngineState) GetAllPendingEBSAttachmentsWithKey() map[string]*apiresource.ResourceAttachment { state.lock.RLock() defer state.lock.RUnlock() return state.allPendingEBSAttachmentsWithKeyUnsafe() } func (state *DockerTaskEngineState) allPendingEBSAttachmentsWithKeyUnsafe() map[string]*apiresource.ResourceAttachment { pendingEBSAttachments := make(map[string]*apiresource.ResourceAttachment) for k, v := range state.ebsAttachments { if !v.IsAttached() || !v.IsSent() { pendingEBSAttachments[k] = v } } return pendingEBSAttachments } // AddEBSAttachment adds the ebs volume to state func (state *DockerTaskEngineState) AddEBSAttachment(ebsAttachment *apiresource.ResourceAttachment) { if ebsAttachment == nil { seelog.Debug("Cannot add empty ebs attachment information") return } state.lock.Lock() defer state.lock.Unlock() volumeId := ebsAttachment.AttachmentProperties[apiresource.VolumeIdKey] if _, ok := state.ebsAttachments[volumeId]; !ok { state.ebsAttachments[volumeId] = ebsAttachment seelog.Debugf("Successfully added EBS attachment: %v", ebsAttachment.EBSToString()) } else { seelog.Debugf("Duplicate ebs attachment information: %v", ebsAttachment.EBSToString()) } } // RemoveEBSAttachment removes the ebs volume from state and stops managing func (state *DockerTaskEngineState) RemoveEBSAttachment(volumeId string) { if volumeId == "" { seelog.Debug("Cannot remove empty ebs attachment information") return } state.lock.Lock() defer state.lock.Unlock() if ebs, ok := state.ebsAttachments[volumeId]; ok { delete(state.ebsAttachments, volumeId) seelog.Debugf("Successfully deleted EBS attachment: %v", ebs.EBSToString()) } else { seelog.Debugf("RemoveEBSAttachment: The requested EBS attachment with volume ID: %v does not exist", volumeId) } } // GetEBSByVolumeId returns the ebs object that matches the given volume ID func (state *DockerTaskEngineState) GetEBSByVolumeId(volumeId string) (*apiresource.ResourceAttachment, bool) { state.lock.RLock() defer state.lock.RUnlock() ebs, ok := state.ebsAttachments[volumeId] return ebs, ok } // GetAllContainerIDs returns all of the Container Ids func (state *DockerTaskEngineState) GetAllContainerIDs() []string { state.lock.RLock() defer state.lock.RUnlock() var ids []string for id := range state.idToTask { ids = append(ids, id) } return ids } // ContainerByID returns an apicontainer.DockerContainer for a given container ID func (state *DockerTaskEngineState) ContainerByID(id string) (*apicontainer.DockerContainer, bool) { state.lock.RLock() defer state.lock.RUnlock() c, ok := state.idToContainer[id] return c, ok } // ContainerMapByArn returns a map of containers belonging to a particular task ARN func (state *DockerTaskEngineState) ContainerMapByArn(arn string) (map[string]*apicontainer.DockerContainer, bool) { state.lock.RLock() defer state.lock.RUnlock() ret, ok := state.taskToID[arn] if !ok { return ret, ok } // Copy the map to avoid data race mc := make(map[string]*apicontainer.DockerContainer) for k, v := range ret { mc[k] = v } return mc, ok } // PulledContainerMapByArn returns a map of pulled containers belonging to a particular task ARN func (state *DockerTaskEngineState) PulledContainerMapByArn(arn string) (map[string]*apicontainer.DockerContainer, bool) { state.lock.RLock() defer state.lock.RUnlock() ret, ok := state.taskToPulledContainer[arn] if !ok { return ret, ok } // Copy the map to avoid data race mc := make(map[string]*apicontainer.DockerContainer) for k, v := range ret { mc[k] = v } return mc, ok } // TaskByShortID retrieves the task of a given docker short container id func (state *DockerTaskEngineState) TaskByShortID(cid string) ([]*apitask.Task, bool) { containerIDs := state.GetAllContainerIDs() var tasks []*apitask.Task for _, id := range containerIDs { if strings.HasPrefix(id, cid) { if task, ok := state.TaskByID(id); ok { tasks = append(tasks, task) } } } return tasks, len(tasks) > 0 } // TaskByID retrieves the task of a given docker container id func (state *DockerTaskEngineState) TaskByID(cid string) (*apitask.Task, bool) { state.lock.RLock() defer state.lock.RUnlock() arn, found := state.idToTask[cid] if !found { return nil, false } return state.taskByArn(arn) } // TaskByArn returns a task for a given ARN func (state *DockerTaskEngineState) TaskByArn(arn string) (*apitask.Task, bool) { state.lock.RLock() defer state.lock.RUnlock() return state.taskByArn(arn) } func (state *DockerTaskEngineState) taskByArn(arn string) (*apitask.Task, bool) { t, ok := state.tasks[arn] return t, ok } // AddTask adds a new task to the state func (state *DockerTaskEngineState) AddTask(task *apitask.Task) { state.lock.Lock() defer state.lock.Unlock() state.tasks[task.Arn] = task } // AddPulledContainer adds a pulled container to the state func (state *DockerTaskEngineState) AddPulledContainer(container *apicontainer.DockerContainer, task *apitask.Task) { state.lock.Lock() defer state.lock.Unlock() if task == nil || container == nil { seelog.Critical("AddPulledContainer called with nil task/container") return } _, exists := state.tasks[task.Arn] if !exists { seelog.Debugf("AddPulledContainer called with unknown task; adding", "arn", task.Arn) state.tasks[task.Arn] = task } existingMap, exists := state.taskToPulledContainer[task.Arn] if !exists { existingMap = make(map[string]*apicontainer.DockerContainer, len(task.Containers)) state.taskToPulledContainer[task.Arn] = existingMap } existingMap[container.Container.Name] = container } // AddContainer adds a container to the state. // If the container has been added with only a name and no docker-id, this // updates the state to include the docker id func (state *DockerTaskEngineState) AddContainer(container *apicontainer.DockerContainer, task *apitask.Task) { state.lock.Lock() defer state.lock.Unlock() if task == nil || container == nil { seelog.Critical("AddContainer called with nil task/container") return } _, exists := state.tasks[task.Arn] if !exists { seelog.Debugf("AddContainer called with unknown task; adding", "arn", task.Arn) state.tasks[task.Arn] = task } _, pulledExist := state.taskToPulledContainer[task.Arn] if pulledExist { seelog.Debugf("Delete a pulled container named %s from the pulled container map associated with the"+ " task ARN %s since AddContainer is called", container.Container.Name, task.Arn) delete(state.taskToPulledContainer[task.Arn], container.Container.Name) if len(state.taskToPulledContainer[task.Arn]) == 0 { delete(state.taskToPulledContainer, task.Arn) } } state.storeIDToContainerTaskUnsafe(container, task) dockerID := container.DockerID v3EndpointID := container.Container.V3EndpointID // stores the v3EndpointID mappings only if container's dockerID exists and container's v3EndpointID has been generated if dockerID != "" && v3EndpointID != "" { state.storeV3EndpointIDToTaskUnsafe(v3EndpointID, task.Arn) state.storeV3EndpointIDToDockerIDUnsafe(v3EndpointID, dockerID) } existingMap, exists := state.taskToID[task.Arn] if !exists { existingMap = make(map[string]*apicontainer.DockerContainer, len(task.Containers)) state.taskToID[task.Arn] = existingMap } existingMap[container.Container.Name] = container } // AddImageState adds an image.ImageState to be stored func (state *DockerTaskEngineState) AddImageState(imageState *image.ImageState) { if imageState == nil { seelog.Debug("Cannot add empty image state") return } if imageState.Image.ImageID == "" { seelog.Debug("Cannot add image state with empty image id") return } state.lock.Lock() defer state.lock.Unlock() state.imageStates[imageState.Image.ImageID] = imageState } // RemoveTask removes a task from this state. It removes all containers and // other associated metadata. It does acquire the write lock. func (state *DockerTaskEngineState) RemoveTask(task *apitask.Task) { state.lock.Lock() defer state.lock.Unlock() task, ok := state.tasks[task.Arn] if !ok { seelog.Warnf("Failed to locate task %s for removal from state", task.Arn) return } delete(state.tasks, task.Arn) if ip, ok := state.taskToIPUnsafe(task.Arn); ok { delete(state.ipToTask, ip) } containerMap, ok := state.taskToID[task.Arn] if !ok { seelog.Warnf("Failed to locate containerMap for task %s for removal from state", task.Arn) return } delete(state.taskToID, task.Arn) for _, dockerContainer := range containerMap { state.removeIDToContainerTaskUnsafe(dockerContainer) // remove v3 endpoint mappings state.removeV3EndpointIDToTaskContainerUnsafe(dockerContainer.Container.V3EndpointID) } delete(state.taskToPulledContainer, task.Arn) } // taskToIPUnsafe gets the ip address for a given task arn func (state *DockerTaskEngineState) taskToIPUnsafe(arn string) (string, bool) { for ip, taskARN := range state.ipToTask { if arn == taskARN { return ip, true } } return "", false } // storeIDToContainerTaskUnsafe stores the container in the idToContainer and idToTask maps. The key to the maps is // either the Docker-generated ID or the agent-generated name (if the ID is not available). If the container is updated // with an ID, a subsequent call to this function will update the map to use the ID as the key. func (state *DockerTaskEngineState) storeIDToContainerTaskUnsafe(container *apicontainer.DockerContainer, task *apitask.Task) { if container.DockerID != "" { // Update the container id to the state state.idToContainer[container.DockerID] = container state.idToTask[container.DockerID] = task.Arn // Remove the previously added name mapping delete(state.idToContainer, container.DockerName) delete(state.idToTask, container.DockerName) } else if container.DockerName != "" { // Update the container name mapping to the state when the ID isn't available state.idToContainer[container.DockerName] = container state.idToTask[container.DockerName] = task.Arn } } // removeIDToContainerTaskUnsafe removes the container from the idToContainer and idToTask maps. They key to the maps // is either the Docker-generated ID or the agent-generated name (if the ID is not available). This function assumes // that the ID takes precedence and will delete by the ID when the ID is available. func (state *DockerTaskEngineState) removeIDToContainerTaskUnsafe(container *apicontainer.DockerContainer) { // The key to these maps is either the Docker ID or agent-generated name. We use the agent-generated name // before a Docker ID is available. key := container.DockerID if key == "" { key = container.DockerName } delete(state.idToTask, key) delete(state.idToContainer, key) } // removeV3EndpointIDToTaskContainerUnsafe removes the container from v3EndpointIDToTask and v3EndpointIDToDockerID maps func (state *DockerTaskEngineState) removeV3EndpointIDToTaskContainerUnsafe(v3EndpointID string) { if v3EndpointID != "" { delete(state.v3EndpointIDToTask, v3EndpointID) delete(state.v3EndpointIDToDockerID, v3EndpointID) } } // RemoveImageState removes an image.ImageState func (state *DockerTaskEngineState) RemoveImageState(imageState *image.ImageState) { if imageState == nil { seelog.Debug("Cannot remove empty image state") return } state.lock.Lock() defer state.lock.Unlock() imageState, ok := state.imageStates[imageState.Image.ImageID] if !ok { seelog.Debug("Image State is not found. Cannot be removed") return } delete(state.imageStates, imageState.Image.ImageID) } // AddTaskIPAddress adds ip adddress for a task arn into the state func (state *DockerTaskEngineState) AddTaskIPAddress(addr string, taskARN string) { state.lock.Lock() defer state.lock.Unlock() state.ipToTask[addr] = taskARN } // GetTaskByIPAddress gets the task arn for an IP address func (state *DockerTaskEngineState) GetTaskByIPAddress(addr string) (string, bool) { state.lock.RLock() defer state.lock.RUnlock() taskARN, ok := state.ipToTask[addr] return taskARN, ok } // GetIPAddressByTaskARN gets the local ip address of a task. func (state *DockerTaskEngineState) GetIPAddressByTaskARN(taskARN string) (string, bool) { state.lock.RLock() defer state.lock.RUnlock() for addr, arn := range state.ipToTask { if arn == taskARN { return addr, true } } return "", false } // storeV3EndpointIDToTaskUnsafe adds v3EndpointID -> taskARN mapping to state func (state *DockerTaskEngineState) storeV3EndpointIDToTaskUnsafe(v3EndpointID, taskARN string) { state.v3EndpointIDToTask[v3EndpointID] = taskARN } // storeV3EndpointIDToContainerNameUnsafe adds v3EndpointID -> dockerID mapping to state func (state *DockerTaskEngineState) storeV3EndpointIDToDockerIDUnsafe(v3EndpointID, dockerID string) { state.v3EndpointIDToDockerID[v3EndpointID] = dockerID } // DockerIDByV3EndpointID returns a docker ID for a given v3 endpoint ID func (state *DockerTaskEngineState) DockerIDByV3EndpointID(v3EndpointID string) (string, bool) { state.lock.RLock() defer state.lock.RUnlock() dockerID, ok := state.v3EndpointIDToDockerID[v3EndpointID] return dockerID, ok } // TaskARNByV3EndpointID returns a taskARN for a given v3 endpoint ID func (state *DockerTaskEngineState) TaskARNByV3EndpointID(v3EndpointID string) (string, bool) { state.lock.RLock() defer state.lock.RUnlock() taskArn, ok := state.v3EndpointIDToTask[v3EndpointID] return taskArn, ok }