ecs-agent/api/attachment/resource/resource_attachment.go (247 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 resource import ( "errors" "fmt" "sync" "time" "github.com/aws/amazon-ecs-agent/ecs-agent/api/attachment" "github.com/aws/amazon-ecs-agent/ecs-agent/logger" "github.com/aws/amazon-ecs-agent/ecs-agent/utils/ttime" ) type ResourceAttachment struct { attachment.AttachmentInfo // AttachmentType is the type of the resource attachment which can be "amazonebs" for EBS attach tasks. AttachmentType string `json:"AttachmentType,omitempty"` // AttachmentProperties is a map storing (name, value) representation of attachment properties. // Each pair is a set of property of one resource attachment. // The "FargateResourceId" is a property name that will be present for all resources. // Other properties can vary based on the resource. // For example, if the attachment is used for an EBS volume resource, the additional properties will be // the customer specified volume size, and the image cache size. AttachmentProperties map[string]string `json:"AttachmentProperties,omitempty"` // ackTimer is used to register the expiration timeout callback for unsuccessful // Resource attachments ackTimer ttime.Timer // guard protects access to fields of this struct guard sync.RWMutex err error } // Agent Communication Service (ACS) can send messages of type ConfirmAttachmentMessage. These messages include // an attachment, and map of associated properties. The below list contains attachment properties which Agent can use // to validate various types of attachments. const ( // Common properties. ResourceTypeName = "resourceType" // Properties specific to volumes. VolumeIdName = "volumeID" DeviceName = "deviceName" // name of the block device on the instance where the volume is attached // Properties specific to resources provisioned by Fargate Control Plane. FargateResourceIdName = "resourceID" // Properties specific to Extensible Ephemeral Storage (EES). VolumeSizeInGiBName = "volumeSizeInGiB" // the total size of the EES (requested size + image cache size) RequestedSizeName = "requestedSizeInGiB" // the customer requested size of extensible ephemeral storage // Properties specific to Elastic Block Service Volumes FileSystemTypeName = "filesystemType" // Properties specific to volumes for EBS attach. VolumeIdKey = "volumeId" VolumeSizeGibKey = "volumeSizeGib" DeviceNameKey = "deviceName" SourceVolumeHostPathKey = "sourceVolumeHostPath" VolumeNameKey = "volumeName" FileSystemKey = "fileSystem" ) var ( allowedFSTypes = map[string]bool{ "xfs": true, "ext2": true, "ext3": true, "ext4": true, "ntfs": true, } ) // getCommonProperties returns the common properties as used for validating a resource. func getCommonProperties() (commonProperties []string) { commonProperties = []string{ ResourceTypeName, } return commonProperties } // getVolumeSpecificProperties returns the properties specific to volume resources. func getVolumeSpecificProperties() (volumeSpecificProperties []string) { volumeSpecificProperties = []string{ VolumeIdName, DeviceName, } return volumeSpecificProperties } // GetVolumeSpecificPropertiesForEBSAttach returns the properties specific to EBS volume resources which will be used // in EBS attach. func GetVolumeSpecificPropertiesForEBSAttach() (volumeSpecificProperties []string) { volumeSpecificProperties = []string{ VolumeIdKey, VolumeSizeGibKey, DeviceNameKey, SourceVolumeHostPathKey, VolumeNameKey, } return volumeSpecificProperties } // getFargateControlPlaneProperties returns the properties specific to resources provisioned by Fargate control plane. func getFargateControlPlaneProperties() (fargateCpProperties []string) { fargateCpProperties = []string{ FargateResourceIdName, } return fargateCpProperties } // getExtensibleEphemeralStorageProperties returns the properties specific to extensible ephemeral storage resources. func getExtensibleEphemeralStorageProperties() (ephemeralStorageProperties []string) { ephemeralStorageProperties = []string{ VolumeSizeInGiBName, RequestedSizeName, } ephemeralStorageProperties = append(ephemeralStorageProperties, getFargateControlPlaneProperties()...) return ephemeralStorageProperties } func getResourceAttachmentLogFields(ra *ResourceAttachment, duration time.Duration) logger.Fields { fields := logger.Fields{ "duration": duration.String(), "attachmentARN": ra.AttachmentARN, "attachmentType": ra.AttachmentType, "attachmentSent": ra.AttachStatusSent, "volumeName": ra.AttachmentProperties[VolumeNameKey], "volumeSizeInGib": ra.AttachmentProperties[VolumeSizeGibKey], "sourceVolumeHostPath": ra.AttachmentProperties[SourceVolumeHostPathKey], "volumeId": ra.AttachmentProperties[VolumeIdKey], "deviceName": ra.AttachmentProperties[DeviceNameKey], "fileSystem": ra.AttachmentProperties[FileSystemKey], "status": ra.Status.String(), "expiresAt": ra.ExpiresAt.Format(time.RFC3339), } return fields } // StartTimer starts the ack timer to record the expiration of resource attachment func (ra *ResourceAttachment) StartTimer(timeoutFunc func()) error { ra.guard.Lock() defer ra.guard.Unlock() if ra.ackTimer != nil { // The timer has already been initialized, do nothing return nil } now := time.Now() duration := ra.ExpiresAt.Sub(now) if duration <= 0 { return fmt.Errorf("resource attachment: timer expiration is in the past; expiration [%s] < now [%s]", ra.ExpiresAt.String(), now.String()) } logger.Info("Starting resource attachment ack timer", getResourceAttachmentLogFields(ra, duration)) ra.ackTimer = time.AfterFunc(duration, timeoutFunc) return nil } // Initialize initializes the fields that can't be populated from loading state file. // Notably, this initializes the ack timer so that if we time out waiting for the resource to be attached, the attachment // can be removed from state. func (ra *ResourceAttachment) Initialize(timeoutFunc func()) error { ra.guard.Lock() defer ra.guard.Unlock() if ra.AttachStatusSent { // resource attachment status has been sent, no need to start ack timer. return nil } now := time.Now() duration := ra.ExpiresAt.Sub(now) if duration <= 0 { return errors.New("resource attachment has already expired") } logger.Info("Starting Resource attachment ack timer", getResourceAttachmentLogFields(ra, duration)) ra.ackTimer = time.AfterFunc(duration, timeoutFunc) return nil } // IsSent checks if the resource attachment attached status has been sent func (ra *ResourceAttachment) IsSent() bool { ra.guard.RLock() defer ra.guard.RUnlock() return ra.AttachStatusSent } // SetSentStatus marks the resource attachment attached status has been sent func (ra *ResourceAttachment) SetSentStatus() { ra.guard.Lock() defer ra.guard.Unlock() ra.AttachStatusSent = true } // IsAttached checks if the resource attachment has been found attached on the host func (ra *ResourceAttachment) IsAttached() bool { ra.guard.RLock() defer ra.guard.RUnlock() return ra.Status == attachment.AttachmentAttached } // SetAttachedStatus marks the resouce attachment as attached once it's been found on the host func (ra *ResourceAttachment) SetAttachedStatus() { ra.guard.Lock() defer ra.guard.Unlock() ra.Status = attachment.AttachmentAttached } // StopAckTimer stops the ack timer set on the resource attachment func (ra *ResourceAttachment) StopAckTimer() { ra.guard.Lock() defer ra.guard.Unlock() ra.ackTimer.Stop() } // HasExpired returns true if the resource attachment object has exceeded the // threshold for notifying the backend of the attachment func (ra *ResourceAttachment) HasExpired() bool { ra.guard.RLock() defer ra.guard.RUnlock() return time.Now().After(ra.ExpiresAt) } // SetError sets the error for a resource attachment if it can't be found. func (ra *ResourceAttachment) SetError(err error) { ra.guard.Lock() defer ra.guard.Unlock() ra.err = err } // GetError returns the error field for a resource attachment. func (ra *ResourceAttachment) GetError() error { ra.guard.RLock() defer ra.guard.RUnlock() return ra.err } // EBSToString returns a string representation of an EBS volume resource attachment. func (ra *ResourceAttachment) EBSToString() string { ra.guard.RLock() defer ra.guard.RUnlock() return ra.ebsToStringUnsafe() } func (ra *ResourceAttachment) ebsToStringUnsafe() string { return fmt.Sprintf( "Resource Attachment: arn=%s attachmentType=%s attachmentSent=%t volumeName=%s fileSystem=%s volumeId=%s volumeSizeInGib=%s deviceName=%s sourceVolumeHostPath=%s status=%s expiresAt=%s error=%v", ra.AttachmentARN, ra.AttachmentType, ra.AttachStatusSent, ra.AttachmentProperties[VolumeNameKey], ra.AttachmentProperties[FileSystemKey], ra.AttachmentProperties[VolumeIdKey], ra.AttachmentProperties[VolumeSizeGibKey], ra.AttachmentProperties[DeviceNameKey], ra.AttachmentProperties[SourceVolumeHostPathKey], ra.Status.String(), ra.ExpiresAt.Format(time.RFC3339), ra.err) } // GetAttachmentProperties returns the specific attachment property of the resource attachment object func (ra *ResourceAttachment) GetAttachmentProperties(key string) string { ra.guard.RLock() defer ra.guard.RUnlock() val, ok := ra.AttachmentProperties[key] if ok { return val } return "" } func (ra *ResourceAttachment) GetAttachmentType() string { ra.guard.RLock() defer ra.guard.RUnlock() return ra.AttachmentType } func (ra *ResourceAttachment) SetDeviceName(deviceName string) { ra.guard.Lock() defer ra.guard.Unlock() ra.AttachmentProperties[DeviceNameKey] = deviceName } func (ra *ResourceAttachment) GetAttachmentARN() string { ra.guard.RLock() defer ra.guard.RUnlock() return ra.AttachmentARN } func (ra *ResourceAttachment) GetStatus() attachment.AttachmentStatus { ra.guard.RLock() defer ra.guard.RUnlock() return ra.Status } func (ra *ResourceAttachment) GetExpiresAt() time.Time { ra.guard.RLock() defer ra.guard.RUnlock() return ra.ExpiresAt } func (ra *ResourceAttachment) GetClusterARN() string { ra.guard.RLock() defer ra.guard.RUnlock() return ra.ClusterARN } func (ra *ResourceAttachment) GetContainerInstanceARN() string { ra.guard.RLock() defer ra.guard.RUnlock() return ra.ContainerInstanceARN } // should attach when not attached, and not sent/not expired func (ra *ResourceAttachment) ShouldAttach() bool { ra.guard.RLock() defer ra.guard.RUnlock() return !(ra.Status == attachment.AttachmentAttached) && !ra.AttachStatusSent && !(time.Now().After(ra.ExpiresAt)) } // should notify when attached, and not sent/not expired func (ra *ResourceAttachment) ShouldNotify() bool { ra.guard.RLock() defer ra.guard.RUnlock() return (ra.Status == attachment.AttachmentAttached) && !ra.AttachStatusSent && !(time.Now().After(ra.ExpiresAt)) } func (ra *ResourceAttachment) String() string { ra.guard.RLock() defer ra.guard.RUnlock() return ra.stringUnsafe() } func (ra *ResourceAttachment) GetAttachmentStatus() attachment.AttachmentStatus { ra.guard.RLock() defer ra.guard.RUnlock() return ra.Status } // stringUnsafe returns a string representation of the ENI Attachment func (ra *ResourceAttachment) stringUnsafe() string { return fmt.Sprintf( "Resource Attachment: attachment=%s attachmentType=%s attachmentSent=%t status=%s expiresAt=%s", ra.AttachmentARN, ra.AttachmentType, ra.AttachStatusSent, ra.Status.String(), ra.ExpiresAt.Format(time.RFC3339)) }