diff --git a/agent/api/task/task.go b/agent/api/task/task.go index cb3253e982e..5b10387f3a0 100644 --- a/agent/api/task/task.go +++ b/agent/api/task/task.go @@ -3435,6 +3435,22 @@ func (task *Task) IsServiceConnectEnabled() bool { // Is EBS Task Attach enabled returns true if this task has EBS volume configuration in its ACS payload. // TODO as more daemons come online, we'll want a generic handler these bool checks and payload handling func (task *Task) IsEBSTaskAttachEnabled() bool { + task.lock.RLock() + defer task.lock.RUnlock() + return task.isEBSTaskAttachEnabledUnsafe() +} + +func (task *Task) isEBSTaskAttachEnabledUnsafe() bool { + logger.Debug("Checking if there are any ebs volume configs") + for _, tv := range task.Volumes { + switch tv.Volume.(type) { + case *taskresourcevolume.EBSTaskVolumeConfig: + logger.Debug("found ebs volume config") + return true + default: + continue + } + } return false } diff --git a/agent/ebs/watcher.go b/agent/ebs/watcher.go index 3e87ef15478..dfd00d5d515 100644 --- a/agent/ebs/watcher.go +++ b/agent/ebs/watcher.go @@ -120,6 +120,8 @@ func (w *EBSWatcher) HandleEBSResourceAttachment(ebs *apiebs.ResourceAttachment) return nil } +// overrideDeviceName() will replace the device name that we've received from ACS with the actual device name found on the host. +// This is needed for NodeStageVolume and what we received from ACS won't be what we see on the host instance. func (w *EBSWatcher) overrideDeviceName(foundVolumes map[string]string) { for volumeId, deviceName := range foundVolumes { ebs, ok := w.agentState.GetEBSByVolumeId(volumeId) diff --git a/agent/engine/docker_task_engine.go b/agent/engine/docker_task_engine.go index e3f4d0d9d53..16eb224d2e7 100644 --- a/agent/engine/docker_task_engine.go +++ b/agent/engine/docker_task_engine.go @@ -1143,7 +1143,8 @@ func (engine *DockerTaskEngine) AddTask(task *apitask.Task) { engine.emitTaskEvent(task, err.Error()) return } - if task.IsEBSTaskAttachEnabled() { + // TODO: This will be fixed in a future PR. For now it will always be false. + if task.IsEBSTaskAttachEnabled() && false { if csiTask, ok := engine.loadedDaemonTasks["ebs-csi-driver"]; ok { logger.Info("engine ebs CSI driver is running", logger.Fields{ field.TaskID: csiTask.GetID(), diff --git a/agent/engine/dockerstate/docker_task_engine_state.go b/agent/engine/dockerstate/docker_task_engine_state.go index 92c7b10f7df..c64f090707b 100644 --- a/agent/engine/dockerstate/docker_task_engine_state.go +++ b/agent/engine/dockerstate/docker_task_engine_state.go @@ -334,7 +334,7 @@ func (state *DockerTaskEngineState) AddEBSAttachment(ebsAttachment *apiresource. } state.lock.Lock() defer state.lock.Unlock() - volumeId := ebsAttachment.AttachmentProperties[apiresource.VolumeIdName] + volumeId := ebsAttachment.AttachmentProperties[apiresource.VolumeIdKey] if _, ok := state.ebsAttachments[volumeId]; !ok { state.ebsAttachments[volumeId] = ebsAttachment seelog.Debugf("Successfully added EBS attachment: %v", ebsAttachment.EBSToString()) diff --git a/agent/engine/dockerstate/dockerstate_test.go b/agent/engine/dockerstate/dockerstate_test.go index 02df17bb720..c70c67312b3 100644 --- a/agent/engine/dockerstate/dockerstate_test.go +++ b/agent/engine/dockerstate/dockerstate_test.go @@ -31,12 +31,12 @@ import ( var ( testAttachmentProperties = map[string]string{ - apiresource.ResourceTypeName: apiresource.ElasticBlockStorage, - apiresource.RequestedSizeName: "5", - apiresource.VolumeSizeInGiBName: "7", - apiresource.DeviceName: "/dev/nvme0n0", - apiresource.VolumeIdName: "vol-123", - apiresource.FileSystemTypeName: "testXFS", + apiresource.VolumeNameKey: "myCoolVolume", + apiresource.SourceVolumeHostPathKey: "/testpath", + apiresource.VolumeSizeGibKey: "7", + apiresource.DeviceNameKey: "/dev/nvme0n0", + apiresource.VolumeIdKey: "vol-123", + apiresource.FileSystemKey: "testXFS", } ) @@ -138,6 +138,7 @@ func TestAddRemoveEBSAttachment(t *testing.T) { AttachmentARN: "ebs1", }, AttachmentProperties: testAttachmentProperties, + AttachmentType: apiresource.EBSTaskAttach, } state.AddEBSAttachment(attachment) @@ -150,7 +151,7 @@ func TestAddRemoveEBSAttachment(t *testing.T) { assert.False(t, ok) assert.Nil(t, ebs) - state.RemoveEBSAttachment(attachment.AttachmentProperties[apiresource.VolumeIdName]) + state.RemoveEBSAttachment(attachment.AttachmentProperties[apiresource.VolumeIdKey]) assert.Len(t, state.(*DockerTaskEngineState).GetAllEBSAttachments(), 0) ebs, ok = state.GetEBSByVolumeId("vol-123") assert.False(t, ok) @@ -168,15 +169,16 @@ func TestAddPendingEBSAttachment(t *testing.T) { Status: status.AttachmentNone, }, AttachmentProperties: testAttachmentProperties, + AttachmentType: apiresource.EBSTaskAttach, } testSentAttachmentProperties := map[string]string{ - apiresource.ResourceTypeName: apiresource.ElasticBlockStorage, - apiresource.RequestedSizeName: "3", - apiresource.VolumeSizeInGiBName: "9", - apiresource.DeviceName: "/dev/nvme1n0", - apiresource.VolumeIdName: "vol-456", - apiresource.FileSystemTypeName: "testXFS2", + apiresource.VolumeNameKey: "myCoolVolume", + apiresource.SourceVolumeHostPathKey: "/testpath2", + apiresource.VolumeSizeGibKey: "7", + apiresource.DeviceNameKey: "/dev/nvme1n0", + apiresource.VolumeIdKey: "vol-456", + apiresource.FileSystemKey: "testXFS", } foundAttachment := &apiresource.ResourceAttachment{ @@ -187,6 +189,7 @@ func TestAddPendingEBSAttachment(t *testing.T) { Status: status.AttachmentAttached, }, AttachmentProperties: testSentAttachmentProperties, + AttachmentType: apiresource.EBSTaskAttach, } state.AddEBSAttachment(pendingAttachment) diff --git a/agent/engine/task_manager.go b/agent/engine/task_manager.go index a46eba824c8..c496da9bf75 100644 --- a/agent/engine/task_manager.go +++ b/agent/engine/task_manager.go @@ -17,6 +17,7 @@ import ( "context" "fmt" "io" + "path/filepath" "strconv" "strings" "sync" @@ -34,10 +35,12 @@ import ( "github.com/aws/amazon-ecs-agent/agent/statechange" "github.com/aws/amazon-ecs-agent/agent/taskresource" resourcestatus "github.com/aws/amazon-ecs-agent/agent/taskresource/status" + taskresourcevolume "github.com/aws/amazon-ecs-agent/agent/taskresource/volume" apicontainerstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/container/status" apierrors "github.com/aws/amazon-ecs-agent/ecs-agent/api/errors" apitaskstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/task/status" "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" + "github.com/aws/amazon-ecs-agent/ecs-agent/csiclient" "github.com/aws/amazon-ecs-agent/ecs-agent/eventstream" "github.com/aws/amazon-ecs-agent/ecs-agent/logger" "github.com/aws/amazon-ecs-agent/ecs-agent/logger/field" @@ -252,6 +255,12 @@ func (mtask *managedTask) overseeTask() { mtask.engine.wakeUpTaskQueueMonitor() // TODO: make this idempotent on agent restart go mtask.releaseIPInIPAM() + + err := mtask.UnstageVolumes() + if err != nil { + logger.Error(fmt.Sprintf("Unable to unstage volumes: %v", err)) + } + mtask.cleanupTask(retry.AddJitter(mtask.cfg.TaskCleanupWaitDuration, mtask.cfg.TaskCleanupWaitDurationJitter)) } @@ -1559,3 +1568,38 @@ func (mtask *managedTask) waitForStopReported() bool { } return taskStopped } + +func (mtask *managedTask) UnstageVolumes() error { + task := mtask.Task + if task == nil { + return fmt.Errorf("task not is not managed") + } + if !task.IsEBSTaskAttachEnabled() { + logger.Debug("Task is not EBS-backed. Skip NodeUnstageVolume.") + return nil + } + csiClient := csiclient.NewCSIClient(filepath.Join(csiclient.SocketHostPath, csiclient.ImageName, csiclient.SocketName)) + for _, tv := range task.Volumes { + switch tv.Volume.(type) { + case *taskresourcevolume.EBSTaskVolumeConfig: + ebsCfg := tv.Volume.(*taskresourcevolume.EBSTaskVolumeConfig) + volumeId := ebsCfg.VolumeId + hostPath := ebsCfg.Source() + err := mtask.unstageVolumeWithTimeout(&csiClient, volumeId, hostPath) + if err != nil { + logger.Error("Unable to unstage volume", logger.Fields{ + "Task": task.String(), + "Error": err, + }) + return err + } + } + } + return nil +} + +func (mtask *managedTask) unstageVolumeWithTimeout(csiClient csiclient.CSIClient, volumeId, hostPath string) error { + derivedCtx, cancel := context.WithTimeout(mtask.ctx, time.Second*3) + defer cancel() + return csiClient.NodeUnstageVolume(derivedCtx, volumeId, hostPath) +} diff --git a/agent/stats/engine.go b/agent/stats/engine.go index 6c4fc25fbf8..6687f4d8db3 100644 --- a/agent/stats/engine.go +++ b/agent/stats/engine.go @@ -37,6 +37,7 @@ import ( ecsengine "github.com/aws/amazon-ecs-agent/agent/engine" "github.com/aws/amazon-ecs-agent/agent/stats/resolver" apicontainerstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/container/status" + "github.com/aws/amazon-ecs-agent/ecs-agent/csiclient" "github.com/aws/amazon-ecs-agent/ecs-agent/eventstream" "github.com/aws/amazon-ecs-agent/ecs-agent/stats" "github.com/aws/amazon-ecs-agent/ecs-agent/tcs/model/ecstcs" @@ -112,6 +113,8 @@ type DockerStatsEngine struct { // channels to send metrics to TACS Client metricsChannel chan<- ecstcs.TelemetryMessage healthChannel chan<- ecstcs.HealthMessage + + csiClient csiclient.CSIClient } // ResolveTask resolves the api task object, given container id. @@ -572,12 +575,15 @@ func (engine *DockerStatsEngine) GetInstanceMetrics(includeServiceConnectStats b continue } + volMetrics := engine.getEBSVolumeMetrics(taskArn) + metricTaskArn := taskArn taskMetric := &ecstcs.TaskMetric{ TaskArn: &metricTaskArn, TaskDefinitionFamily: &taskDef.family, TaskDefinitionVersion: &taskDef.version, ContainerMetrics: containerMetrics, + VolumeMetrics: volMetrics, } if includeServiceConnectStats { diff --git a/agent/stats/engine_test.go b/agent/stats/engine_test.go index 1abc76148f2..17e3ded1391 100644 --- a/agent/stats/engine_test.go +++ b/agent/stats/engine_test.go @@ -31,6 +31,7 @@ import ( mock_resolver "github.com/aws/amazon-ecs-agent/agent/stats/resolver/mock" apicontainerstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/container/status" apitaskstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/task/status" + "github.com/aws/amazon-ecs-agent/ecs-agent/csiclient" ni "github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/networkinterface" "github.com/aws/amazon-ecs-agent/ecs-agent/tcs/model/ecstcs" "github.com/aws/aws-sdk-go/aws" @@ -62,6 +63,9 @@ func TestStatsEngineAddRemoveContainers(t *testing.T) { resolver.EXPECT().ResolveTask("c1").AnyTimes().Return(t1, nil) resolver.EXPECT().ResolveTask("c2").AnyTimes().Return(t1, nil) resolver.EXPECT().ResolveTask("c3").AnyTimes().Return(t2, nil) + resolver.EXPECT().ResolveTaskByARN("t1").AnyTimes().Return(t1, nil) + resolver.EXPECT().ResolveTaskByARN("t2").AnyTimes().Return(t2, nil) + resolver.EXPECT().ResolveTaskByARN("t3").AnyTimes().Return(t3, nil) resolver.EXPECT().ResolveTask("c4").AnyTimes().Return(nil, fmt.Errorf("unmapped container")) resolver.EXPECT().ResolveTask("c5").AnyTimes().Return(t2, nil) resolver.EXPECT().ResolveTask("c6").AnyTimes().Return(t3, nil) @@ -82,6 +86,7 @@ func TestStatsEngineAddRemoveContainers(t *testing.T) { engine.client = mockDockerClient engine.cluster = defaultCluster engine.containerInstanceArn = defaultContainerInstance + engine.csiClient = csiclient.NewDummyCSIClient() defer engine.removeAll() engine.addAndStartStatsContainer("c1") diff --git a/agent/stats/engine_unix.go b/agent/stats/engine_unix.go new file mode 100644 index 00000000000..6249d4aa4e8 --- /dev/null +++ b/agent/stats/engine_unix.go @@ -0,0 +1,104 @@ +//go:build linux +// +build linux + +// 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 stats + +import ( + "context" + "fmt" + "path/filepath" + "time" + + apitask "github.com/aws/amazon-ecs-agent/agent/api/task" + taskresourcevolume "github.com/aws/amazon-ecs-agent/agent/taskresource/volume" + "github.com/aws/amazon-ecs-agent/ecs-agent/csiclient" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger" + "github.com/aws/amazon-ecs-agent/ecs-agent/tcs/model/ecstcs" + + "github.com/aws/aws-sdk-go/aws" +) + +func (engine *DockerStatsEngine) getEBSVolumeMetrics(taskArn string) []*ecstcs.VolumeMetric { + task, err := engine.resolver.ResolveTaskByARN(taskArn) + if err != nil { + logger.Error(fmt.Sprintf("Unable to get corresponding task from dd with task arn: %s", taskArn)) + return nil + } + + if !task.IsEBSTaskAttachEnabled() { + logger.Debug("Task not EBS-backed, skip gathering EBS volume metrics.", logger.Fields{ + "taskArn": taskArn, + }) + return nil + } + + if engine.csiClient == nil { + client := csiclient.NewCSIClient(filepath.Join(csiclient.SocketHostPath, csiclient.ImageName, csiclient.SocketName)) + engine.csiClient = &client + } + return engine.fetchEBSVolumeMetrics(task, taskArn) +} + +func (engine *DockerStatsEngine) fetchEBSVolumeMetrics(task *apitask.Task, taskArn string) []*ecstcs.VolumeMetric { + var metrics []*ecstcs.VolumeMetric + for _, tv := range task.Volumes { + // TODO: Include Getters within the TaskVolume interface so that we don't need to have these type casts. + // (i.e. getVolumeId()) + switch tv.Volume.(type) { + case *taskresourcevolume.EBSTaskVolumeConfig: + ebsCfg := tv.Volume.(*taskresourcevolume.EBSTaskVolumeConfig) + volumeId := ebsCfg.VolumeId + hostPath := ebsCfg.Source() + metric, err := engine.getVolumeMetricsWithTimeout(volumeId, hostPath) + if err != nil { + logger.Error("Failed to gather metrics for EBS volume", logger.Fields{ + "VolumeId": volumeId, + "SourceVolumeHostPath": hostPath, + "Error": err, + }) + continue + } + usedBytes := aws.Float64((float64)(metric.Used)) + totalBytes := aws.Float64((float64)(metric.Capacity)) + metrics = append(metrics, &ecstcs.VolumeMetric{ + VolumeId: aws.String(volumeId), + VolumeName: aws.String(ebsCfg.VolumeName), + Utilized: &ecstcs.UDoubleCWStatsSet{ + Max: usedBytes, + Min: usedBytes, + SampleCount: aws.Int64(1), + Sum: usedBytes, + }, + Size: &ecstcs.UDoubleCWStatsSet{ + Max: totalBytes, + Min: totalBytes, + SampleCount: aws.Int64(1), + Sum: totalBytes, + }, + }) + default: + continue + } + } + return metrics +} + +func (engine *DockerStatsEngine) getVolumeMetricsWithTimeout(volumeId, hostPath string) (*csiclient.Metrics, error) { + derivedCtx, cancel := context.WithTimeout(engine.ctx, time.Second*1) + // releases resources if GetVolumeMetrics finishes before timeout + defer cancel() + return engine.csiClient.GetVolumeMetrics(derivedCtx, volumeId, hostPath) +} diff --git a/agent/stats/engine_unix_test.go b/agent/stats/engine_unix_test.go index 7427dfb72c7..059130d1545 100644 --- a/agent/stats/engine_unix_test.go +++ b/agent/stats/engine_unix_test.go @@ -26,8 +26,13 @@ import ( "github.com/aws/amazon-ecs-agent/agent/config" mock_dockerapi "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi/mocks" mock_resolver "github.com/aws/amazon-ecs-agent/agent/stats/resolver/mock" + taskresourcevolume "github.com/aws/amazon-ecs-agent/agent/taskresource/volume" + apiresource "github.com/aws/amazon-ecs-agent/ecs-agent/api/resource" apitaskstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/task/status" + "github.com/aws/amazon-ecs-agent/ecs-agent/csiclient" ni "github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/networkinterface" + "github.com/aws/amazon-ecs-agent/ecs-agent/tcs/model/ecstcs" + "github.com/aws/aws-sdk-go/aws" "github.com/docker/docker/api/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" @@ -166,3 +171,66 @@ func TestServiceConnectWithDisabledMetrics(t *testing.T) { assert.Len(t, engine.tasksToHealthCheckContainers, 1) assert.Len(t, engine.taskToServiceConnectStats, 1) } + +func TestFetchEBSVolumeMetrics(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + resolver := mock_resolver.NewMockContainerMetadataResolver(mockCtrl) + mockDockerClient := mock_dockerapi.NewMockDockerClient(mockCtrl) + t1 := &apitask.Task{ + Arn: "t1", + Volumes: []apitask.TaskVolume{ + { + Name: "1", + Type: apiresource.EBSTaskAttach, + Volume: &taskresourcevolume.EBSTaskVolumeConfig{ + VolumeId: "vol-12345", + VolumeName: "test-volume", + VolumeSizeGib: "10", + SourceVolumeHostPath: "taskarn_vol-12345", + DeviceName: "/dev/nvme1n1", + FileSystem: "ext4", + }, + }, + }, + } + + resolver.EXPECT().ResolveTaskByARN("t1").AnyTimes().Return(t1, nil) + mockDockerClient.EXPECT().Stats(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + + engine := NewDockerStatsEngine(&cfg, nil, eventStream("TestFetchEBSVolumeMetrics"), nil, nil) + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + engine.ctx = ctx + engine.resolver = resolver + engine.cluster = defaultCluster + engine.containerInstanceArn = defaultContainerInstance + engine.client = mockDockerClient + engine.csiClient = csiclient.NewDummyCSIClient() + + expectedUsedBytes := aws.Float64(15 * 1024 * 1024 * 1024) + expectedTotalBytes := aws.Float64(20 * 1024 * 1024 * 1024) + expectedMetrics := []*ecstcs.VolumeMetric{ + { + VolumeId: aws.String("vol-12345"), + VolumeName: aws.String("test-volume"), + Utilized: &ecstcs.UDoubleCWStatsSet{ + Max: expectedUsedBytes, + Min: expectedUsedBytes, + SampleCount: aws.Int64(1), + Sum: expectedUsedBytes, + }, + Size: &ecstcs.UDoubleCWStatsSet{ + Max: expectedTotalBytes, + Min: expectedTotalBytes, + SampleCount: aws.Int64(1), + Sum: expectedTotalBytes, + }, + }, + } + + actualMetrics := engine.fetchEBSVolumeMetrics(t1, "t1") + + assert.Len(t, actualMetrics, 1) + assert.Equal(t, actualMetrics, expectedMetrics) +} diff --git a/agent/stats/engine_windows.go b/agent/stats/engine_windows.go new file mode 100644 index 00000000000..09151eb8ea6 --- /dev/null +++ b/agent/stats/engine_windows.go @@ -0,0 +1,25 @@ +//go:build windows +// +build windows + +// 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 stats + +import ( + "github.com/aws/amazon-ecs-agent/ecs-agent/tcs/model/ecstcs" +) + +func (engine *DockerStatsEngine) getEBSVolumeMetrics(taskArn string) []*ecstcs.VolumeMetric { + return nil +} diff --git a/agent/taskresource/volume/testconst.go b/agent/taskresource/volume/testconst.go index c7d3ccc1b3a..91972608d09 100644 --- a/agent/taskresource/volume/testconst.go +++ b/agent/taskresource/volume/testconst.go @@ -16,7 +16,7 @@ package volume // This file contains constants that are commonly used when testing with EBS volumes for tasks. These constants // should only be called in test files. const ( - TestAttachmentType = "EBSTaskAttach" + TestAttachmentType = "amazonebs" TestVolumeId = "vol-12345" TestVolumeSizeGib = "10" TestSourceVolumeHostPath = "taskarn_vol-12345" diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/csi_client.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/csi_client.go index a0ca3bc5bde..c4401dfb7a4 100644 --- a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/csi_client.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/csi_client.go @@ -20,6 +20,7 @@ import ( "time" "github.com/aws/amazon-ecs-agent/ecs-agent/logger" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/field" "github.com/container-storage-interface/spec/lib/go/csi" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -52,6 +53,7 @@ type CSIClient interface { ) error NodeUnstageVolume(ctx context.Context, volumeId, stagingTargetPath string) error GetVolumeMetrics(ctx context.Context, volumeId string, hostMountPath string) (*Metrics, error) + NodeGetCapabilities(ctx context.Context) (*csi.NodeGetCapabilitiesResponse, error) } // csiClient encapsulates all CSI methods. @@ -64,6 +66,13 @@ func NewCSIClient(socketIn string) csiClient { return csiClient{csiSocket: socketIn} } +// Returns a CSI client configured with default settings. +// The default socket filepath is "/var/run/ecs/ebs-csi-driver/csi-driver.sock". +func NewDefaultCSIClient() CSIClient { + client := NewCSIClient(DefaultSocketFilePath()) + return &client +} + // NodeStageVolume will do following things for the given volume: // 1. format the device if it does not have any, // 2. mount the device to given stagingTargetPath, @@ -190,6 +199,25 @@ func (cc *csiClient) GetVolumeMetrics(ctx context.Context, volumeId string, host }, nil } +// Gets node capabilities of the EBS CSI Driver +func (cc *csiClient) NodeGetCapabilities(ctx context.Context) (*csi.NodeGetCapabilitiesResponse, error) { + conn, err := cc.grpcDialConnect(ctx) + if err != nil { + logger.Error("NodeGetCapabilities: CSI Connection Error", logger.Fields{field.Error: err}) + return nil, err + } + defer conn.Close() + + client := csi.NewNodeClient(conn) + resp, err := client.NodeGetCapabilities(ctx, &csi.NodeGetCapabilitiesRequest{}) + if err != nil { + logger.Error("Could not get EBS CSI node capabilities", logger.Fields{field.Error: err}) + return nil, err + } + + return resp, nil +} + func (cc *csiClient) grpcDialConnect(ctx context.Context) (*grpc.ClientConn, error) { dialer := func(addr string, t time.Duration) (net.Conn, error) { return net.Dial(protocol, addr) diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/csi_client_linux.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/csi_client_linux.go new file mode 100644 index 00000000000..d03748e6890 --- /dev/null +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/csi_client_linux.go @@ -0,0 +1,28 @@ +//go:build linux +// +build linux + +// 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 csiclient + +import "path/filepath" + +const ( + defaultImageName = "ebs-csi-driver" + defaultSocketName = "csi-driver.sock" + defaultSocketHostPath = "/var/run/ecs/" +) + +func DefaultSocketFilePath() string { + return filepath.Join(defaultSocketHostPath, defaultImageName, defaultSocketName) +} diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/csi_client_windows.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/csi_client_windows.go new file mode 100644 index 00000000000..664abcbbf06 --- /dev/null +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/csi_client_windows.go @@ -0,0 +1,20 @@ +//go:build windows +// +build windows + +// 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 csiclient + +func DefaultSocketFilePath() string { + return "unimplemented" // TODO: Windows implementation +} diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/dummy_csiclient.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/dummy_csiclient.go index cb4dd70b220..c0c36ba49e2 100644 --- a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/dummy_csiclient.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/dummy_csiclient.go @@ -16,6 +16,7 @@ package csiclient import ( "context" + "github.com/container-storage-interface/spec/lib/go/csi" v1 "k8s.io/api/core/v1" ) @@ -50,6 +51,10 @@ func (c *dummyCSIClient) NodeUnstageVolume(ctx context.Context, volumeId, stagin return nil } +func (c *dummyCSIClient) NodeGetCapabilities(ctx context.Context) (*csi.NodeGetCapabilitiesResponse, error) { + return &csi.NodeGetCapabilitiesResponse{}, nil +} + func NewDummyCSIClient() CSIClient { return &dummyCSIClient{} } diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/generate_mocks.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/generate_mocks.go new file mode 100644 index 00000000000..3d61e0a78b7 --- /dev/null +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/csiclient/generate_mocks.go @@ -0,0 +1,15 @@ +// 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 csiclient + +//go:generate mockgen -destination=mocks/mocks.go -copyright_file=../../scripts/copyright_file github.com/aws/amazon-ecs-agent/ecs-agent/csiclient CSIClient