Skip to content

Commit 284375c

Browse files
saurabhc123mye956
authored andcommitted
Added the implementation for EBS volume discovery on Windows (#49)
* Added the implementation for EBS volume discovery on Windows * incorporated the review comments. Also fixed the name of the timeout variable. * incorporated the review comments. Also fixed the name of the timeout variable.
1 parent 1b08b87 commit 284375c

File tree

7 files changed

+333
-30
lines changed

7 files changed

+333
-30
lines changed

agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/api/resource/ebs_discovery.go

+21-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/api/resource/ebs_discovery_linux.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/api/resource/ebs_discovery_windows.go

+101-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ecs-agent/api/resource/ebs_discovery.go

+21-13
Original file line numberDiff line numberDiff line change
@@ -15,47 +15,55 @@ package resource
1515

1616
import (
1717
"context"
18-
"errors"
19-
"fmt"
2018
"strings"
2119
"time"
20+
21+
"github.com/aws/amazon-ecs-agent/ecs-agent/logger"
22+
23+
"github.com/pkg/errors"
2224
)
2325

2426
const (
25-
ebsnvmeIDTimeoutDuration = 5 * time.Second
26-
ebsResourceKeyPrefix = "ebs-volume:"
27-
ScanPeriod = 500 * time.Millisecond
27+
ebsVolumeDiscoveryTimeout = 5 * time.Second
28+
ebsResourceKeyPrefix = "ebs-volume:"
29+
ScanPeriod = 500 * time.Millisecond
2830
)
2931

3032
var (
3133
ErrInvalidVolumeID = errors.New("EBS volume IDs do not match")
3234
)
3335

34-
// EBSDiscoveryClient is used to scan and validate if an EBS volume is attached on the host instance.
3536
type EBSDiscoveryClient struct {
3637
ctx context.Context
3738
}
3839

39-
// NewDiscoveryClient creates a new EBSDiscoveryClient object
40-
func NewDiscoveryClient(ctx context.Context) EBSDiscovery {
40+
func NewDiscoveryClient(ctx context.Context) *EBSDiscoveryClient {
4141
return &EBSDiscoveryClient{
4242
ctx: ctx,
4343
}
4444
}
4545

4646
// ScanEBSVolumes will iterate through the entire list of pending EBS volume attachments within the agent state and checks if it's attached on the host.
47-
func ScanEBSVolumes[T GenericEBSAttachmentObject](pendingAttachments map[string]T, dc EBSDiscovery) []string {
47+
func ScanEBSVolumes[T GenericEBSAttachmentObject](t map[string]T, dc EBSDiscovery) []string {
4848
var err error
4949
var foundVolumes []string
50-
for key, ebs := range pendingAttachments {
50+
for key, ebs := range t {
5151
volumeId := strings.TrimPrefix(key, ebsResourceKeyPrefix)
5252
deviceName := ebs.GetAttachmentProperties(DeviceName)
5353
err = dc.ConfirmEBSVolumeIsAttached(deviceName, volumeId)
5454
if err != nil {
55-
if !errors.Is(err, ErrInvalidVolumeID) {
56-
err = fmt.Errorf("%w; failed to confirm if EBS volume is attached to the host", err)
55+
if err == ErrInvalidVolumeID || errors.Cause(err) == ErrInvalidVolumeID {
56+
logger.Warn("Found a different EBS volume attached to the host. Expected EBS volume:", logger.Fields{
57+
"volumeId": volumeId,
58+
"deviceName": deviceName,
59+
})
60+
} else {
61+
logger.Warn("Failed to confirm if EBS volume is attached to the host. ", logger.Fields{
62+
"volumeId": volumeId,
63+
"deviceName": deviceName,
64+
"error": err,
65+
})
5766
}
58-
ebs.SetError(err)
5967
continue
6068
}
6169
foundVolumes = append(foundVolumes, key)

ecs-agent/api/resource/ebs_discovery_linux.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type BDChild struct {
4444
// 2. On xen-based instance we only check by the device name.
4545
func (api *EBSDiscoveryClient) ConfirmEBSVolumeIsAttached(deviceName, volumeID string) error {
4646
var lsblkOut LsblkOutput
47-
ctxWithTimeout, cancel := context.WithTimeout(api.ctx, ebsnvmeIDTimeoutDuration)
47+
ctxWithTimeout, cancel := context.WithTimeout(api.ctx, ebsVolumeDiscoveryTimeout)
4848
defer cancel()
4949

5050
// The lsblk command will output the name and volume ID of all block devices on the host in JSON format

ecs-agent/api/resource/ebs_discovery_windows.go

+101-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,106 @@
1616

1717
package resource
1818

19-
func (api EBSDiscoveryClient) ConfirmEBSVolumeIsAttached(deviceName, volumeID string) error {
19+
import (
20+
"context"
21+
"fmt"
22+
"os/exec"
23+
"strings"
24+
25+
log "github.com/cihub/seelog"
26+
"github.com/pkg/errors"
27+
)
28+
29+
const (
30+
diskNumberOffset = 0
31+
volumeIdOffset = 1
32+
deviceNameOffset = 2
33+
volumeInfoLength = 3
34+
)
35+
36+
func (api *EBSDiscoveryClient) ConfirmEBSVolumeIsAttached(deviceName, volumeID string) error {
37+
ctxWithTimeout, cancel := context.WithTimeout(api.ctx, ebsVolumeDiscoveryTimeout)
38+
defer cancel()
39+
output, err := exec.CommandContext(ctxWithTimeout,
40+
"C:\\PROGRAMDATA\\Amazon\\Tools\\ebsnvme-id.exe").CombinedOutput()
41+
if err != nil {
42+
return errors.Wrapf(err, "failed to run ebsnvme-id.exe: %s", string(output))
43+
}
44+
45+
_, err = parseExecutableOutput(output, volumeID, deviceName)
46+
if err != nil {
47+
return errors.Wrapf(err, "failed to parse ebsnvme-id.exe output for volumeID: %s and deviceName: %s",
48+
volumeID, deviceName)
49+
}
50+
51+
log.Info(fmt.Sprintf("found volume with volumeID: %s and deviceName: %s", volumeID, deviceName))
52+
2053
return nil
2154
}
55+
56+
// parseExecutableOutput parses the output of `ebsnvme-id.exe` and returns the volumeId.
57+
func parseExecutableOutput(output []byte, candidateVolumeId string, candidateDeviceName string) (string, error) {
58+
/* The output of the ebsnvme-id.exe is emitted like the following:
59+
Disk Number: 0
60+
Volume ID: vol-0a1234f340444abcd
61+
Device Name: sda1
62+
63+
Disk Number: 1
64+
Volume ID: vol-abcdef1234567890a
65+
Device Name: /dev/sdf */
66+
67+
out := string(output)
68+
// Replace double line with a single line and split based on single line
69+
volumeInfo := strings.Split(strings.Replace(string(out), "\r\n\r\n", "\r\n", -1), "\r\n")
70+
71+
if len(volumeInfo) < volumeInfoLength {
72+
return "", errors.New("cannot find the volume ID. Encountered error message: " + out)
73+
}
74+
75+
//Read every 3 lines of disk information
76+
for volumeIndex := 0; volumeIndex <= len(volumeInfo)-volumeInfoLength; volumeIndex = volumeIndex + volumeInfoLength {
77+
_, volumeId, deviceName, err := parseSet(volumeInfo[volumeIndex : volumeIndex+volumeInfoLength])
78+
if err != nil {
79+
return "", errors.Wrapf(err, "failed to parse the output for volumeID: %s and deviceName: %s. "+
80+
"Output:%s", candidateVolumeId, candidateDeviceName, out)
81+
}
82+
83+
if volumeId == candidateVolumeId && deviceName == candidateDeviceName {
84+
return volumeId, nil
85+
}
86+
87+
}
88+
89+
return "", errors.New("cannot find the volume ID:" + candidateVolumeId)
90+
}
91+
92+
// parseSet parses the single volume information that is 3 lines long
93+
func parseSet(lines []string) (string, string, string, error) {
94+
if len(lines) != 3 {
95+
return "", "", "", errors.New("the number of entries in the volume information is insufficient to parse. Expected 3 lines")
96+
}
97+
98+
diskNumber, err := parseValue(lines[diskNumberOffset], "Disk Number:")
99+
if err != nil {
100+
return "", "", "", err
101+
}
102+
volumeId, err := parseValue(lines[volumeIdOffset], "Volume ID:")
103+
if err != nil {
104+
return "", "", "", err
105+
}
106+
deviceName, err := parseValue(lines[deviceNameOffset], "Device Name:")
107+
if err != nil {
108+
return "", "", "", err
109+
}
110+
return diskNumber, volumeId, deviceName, nil
111+
}
112+
113+
// parseValue looks for the volume information identifier and replaces it to return just the value
114+
func parseValue(inputBuffer string, stringToTrim string) (string, error) {
115+
// if the input buffer doesn't have the identifier for the information, return an error
116+
if !strings.Contains(inputBuffer, stringToTrim) {
117+
return "", errors.New("output buffer was missing the string:" + stringToTrim)
118+
}
119+
120+
return strings.TrimSpace(strings.Replace(inputBuffer, stringToTrim, "", -1)), nil
121+
}

0 commit comments

Comments
 (0)