mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #35883 from justinsb/aws_strong_volumetype
Automatic merge from submit-queue AWS: strong-typing for k8s vs aws volume ids
This commit is contained in:
commit
f4738ff575
@ -21,6 +21,7 @@ go_library(
|
||||
"log_handler.go",
|
||||
"retry_handler.go",
|
||||
"sets_ippermissions.go",
|
||||
"volumes.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -299,31 +298,31 @@ type Volumes interface {
|
||||
// Attach the disk to the node with the specified NodeName
|
||||
// nodeName can be empty to mean "the instance on which we are running"
|
||||
// Returns the device (e.g. /dev/xvdf) where we attached the volume
|
||||
AttachDisk(diskName string, nodeName types.NodeName, readOnly bool) (string, error)
|
||||
AttachDisk(diskName KubernetesVolumeID, nodeName types.NodeName, readOnly bool) (string, error)
|
||||
// Detach the disk from the node with the specified NodeName
|
||||
// nodeName can be empty to mean "the instance on which we are running"
|
||||
// Returns the device where the volume was attached
|
||||
DetachDisk(diskName string, nodeName types.NodeName) (string, error)
|
||||
DetachDisk(diskName KubernetesVolumeID, nodeName types.NodeName) (string, error)
|
||||
|
||||
// Create a volume with the specified options
|
||||
CreateDisk(volumeOptions *VolumeOptions) (volumeName string, err error)
|
||||
CreateDisk(volumeOptions *VolumeOptions) (volumeName KubernetesVolumeID, err error)
|
||||
// Delete the specified volume
|
||||
// Returns true iff the volume was deleted
|
||||
// If the was not found, returns (false, nil)
|
||||
DeleteDisk(volumeName string) (bool, error)
|
||||
DeleteDisk(volumeName KubernetesVolumeID) (bool, error)
|
||||
|
||||
// Get labels to apply to volume on creation
|
||||
GetVolumeLabels(volumeName string) (map[string]string, error)
|
||||
GetVolumeLabels(volumeName KubernetesVolumeID) (map[string]string, error)
|
||||
|
||||
// Get volume's disk path from volume name
|
||||
// return the device path where the volume is attached
|
||||
GetDiskPath(volumeName string) (string, error)
|
||||
GetDiskPath(volumeName KubernetesVolumeID) (string, error)
|
||||
|
||||
// Check if the volume is already attached to the node with the specified NodeName
|
||||
DiskIsAttached(diskName string, nodeName types.NodeName) (bool, error)
|
||||
DiskIsAttached(diskName KubernetesVolumeID, nodeName types.NodeName) (bool, error)
|
||||
|
||||
// Check if a list of volumes are attached to the node with the specified NodeName
|
||||
DisksAreAttached(diskNames []string, nodeName types.NodeName) (map[string]bool, error)
|
||||
DisksAreAttached(diskNames []KubernetesVolumeID, nodeName types.NodeName) (map[KubernetesVolumeID]bool, error)
|
||||
}
|
||||
|
||||
// InstanceGroups is an interface for managing cloud-managed instance groups / autoscaling instance groups
|
||||
@ -365,7 +364,7 @@ type Cloud struct {
|
||||
// attached, to avoid a race condition where we assign a device mapping
|
||||
// and then get a second request before we attach the volume
|
||||
attachingMutex sync.Mutex
|
||||
attaching map[types.NodeName]map[mountDevice]string
|
||||
attaching map[types.NodeName]map[mountDevice]awsVolumeID
|
||||
}
|
||||
|
||||
var _ Volumes = &Cloud{}
|
||||
@ -797,7 +796,7 @@ func newAWSCloud(config io.Reader, awsServices Services) (*Cloud, error) {
|
||||
cfg: cfg,
|
||||
region: regionName,
|
||||
|
||||
attaching: make(map[types.NodeName]map[mountDevice]string),
|
||||
attaching: make(map[types.NodeName]map[mountDevice]awsVolumeID),
|
||||
}
|
||||
|
||||
selfAWSInstance, err := awsCloud.buildSelfAWSInstance()
|
||||
@ -1168,7 +1167,7 @@ func (i *awsInstance) describeInstance() (*ec2.Instance, error) {
|
||||
// Gets the mountDevice already assigned to the volume, or assigns an unused mountDevice.
|
||||
// If the volume is already assigned, this will return the existing mountDevice with alreadyAttached=true.
|
||||
// Otherwise the mountDevice is assigned by finding the first available mountDevice, and it is returned with alreadyAttached=false.
|
||||
func (c *Cloud) getMountDevice(i *awsInstance, volumeID string, assign bool) (assigned mountDevice, alreadyAttached bool, err error) {
|
||||
func (c *Cloud) getMountDevice(i *awsInstance, volumeID awsVolumeID, assign bool) (assigned mountDevice, alreadyAttached bool, err error) {
|
||||
instanceType := i.getInstanceType()
|
||||
if instanceType == nil {
|
||||
return "", false, fmt.Errorf("could not get instance type for instance: %s", i.awsID)
|
||||
@ -1178,7 +1177,7 @@ func (c *Cloud) getMountDevice(i *awsInstance, volumeID string, assign bool) (as
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
deviceMappings := map[mountDevice]string{}
|
||||
deviceMappings := map[mountDevice]awsVolumeID{}
|
||||
for _, blockDevice := range info.BlockDeviceMappings {
|
||||
name := aws.StringValue(blockDevice.DeviceName)
|
||||
if strings.HasPrefix(name, "/dev/sd") {
|
||||
@ -1190,7 +1189,7 @@ func (c *Cloud) getMountDevice(i *awsInstance, volumeID string, assign bool) (as
|
||||
if len(name) < 1 || len(name) > 2 {
|
||||
glog.Warningf("Unexpected EBS DeviceName: %q", aws.StringValue(blockDevice.DeviceName))
|
||||
}
|
||||
deviceMappings[mountDevice(name)] = aws.StringValue(blockDevice.Ebs.VolumeId)
|
||||
deviceMappings[mountDevice(name)] = awsVolumeID(aws.StringValue(blockDevice.Ebs.VolumeId))
|
||||
}
|
||||
|
||||
// We lock to prevent concurrent mounts from conflicting
|
||||
@ -1236,7 +1235,7 @@ func (c *Cloud) getMountDevice(i *awsInstance, volumeID string, assign bool) (as
|
||||
|
||||
attaching := c.attaching[i.nodeName]
|
||||
if attaching == nil {
|
||||
attaching = make(map[mountDevice]string)
|
||||
attaching = make(map[mountDevice]awsVolumeID)
|
||||
c.attaching[i.nodeName] = attaching
|
||||
}
|
||||
attaching[chosen] = volumeID
|
||||
@ -1247,7 +1246,7 @@ func (c *Cloud) getMountDevice(i *awsInstance, volumeID string, assign bool) (as
|
||||
|
||||
// endAttaching removes the entry from the "attachments in progress" map
|
||||
// It returns true if it was found (and removed), false otherwise
|
||||
func (c *Cloud) endAttaching(i *awsInstance, volumeID string, mountDevice mountDevice) bool {
|
||||
func (c *Cloud) endAttaching(i *awsInstance, volumeID awsVolumeID, mountDevice mountDevice) bool {
|
||||
c.attachingMutex.Lock()
|
||||
defer c.attachingMutex.Unlock()
|
||||
|
||||
@ -1272,44 +1271,16 @@ type awsDisk struct {
|
||||
ec2 EC2
|
||||
|
||||
// Name in k8s
|
||||
name string
|
||||
name KubernetesVolumeID
|
||||
// id in AWS
|
||||
awsID string
|
||||
awsID awsVolumeID
|
||||
}
|
||||
|
||||
func newAWSDisk(aws *Cloud, name string) (*awsDisk, error) {
|
||||
// name looks like aws://availability-zone/id
|
||||
|
||||
// The original idea of the URL-style name was to put the AZ into the
|
||||
// host, so we could find the AZ immediately from the name without
|
||||
// querying the API. But it turns out we don't actually need it for
|
||||
// multi-AZ clusters, as we put the AZ into the labels on the PV instead.
|
||||
// However, if in future we want to support multi-AZ cluster
|
||||
// volume-awareness without using PersistentVolumes, we likely will
|
||||
// want the AZ in the host.
|
||||
|
||||
if !strings.HasPrefix(name, "aws://") {
|
||||
name = "aws://" + "" + "/" + name
|
||||
}
|
||||
url, err := url.Parse(name)
|
||||
func newAWSDisk(aws *Cloud, name KubernetesVolumeID) (*awsDisk, error) {
|
||||
awsID, err := name.mapToAWSVolumeID()
|
||||
if err != nil {
|
||||
// TODO: Maybe we should pass a URL into the Volume functions
|
||||
return nil, fmt.Errorf("Invalid disk name (%s): %v", name, err)
|
||||
return nil, err
|
||||
}
|
||||
if url.Scheme != "aws" {
|
||||
return nil, fmt.Errorf("Invalid scheme for AWS volume (%s)", name)
|
||||
}
|
||||
|
||||
awsID := url.Path
|
||||
if len(awsID) > 1 && awsID[0] == '/' {
|
||||
awsID = awsID[1:]
|
||||
}
|
||||
|
||||
// TODO: Regex match?
|
||||
if strings.Contains(awsID, "/") || !strings.HasPrefix(awsID, "vol-") {
|
||||
return nil, fmt.Errorf("Invalid format for AWS volume (%s)", name)
|
||||
}
|
||||
|
||||
disk := &awsDisk{ec2: aws.ec2, name: name, awsID: awsID}
|
||||
return disk, nil
|
||||
}
|
||||
@ -1319,7 +1290,7 @@ func (d *awsDisk) describeVolume() (*ec2.Volume, error) {
|
||||
volumeID := d.awsID
|
||||
|
||||
request := &ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{&volumeID},
|
||||
VolumeIds: []*string{volumeID.awsString()},
|
||||
}
|
||||
|
||||
volumes, err := d.ec2.DescribeVolumes(request)
|
||||
@ -1400,7 +1371,7 @@ func (d *awsDisk) waitForAttachmentStatus(status string) (*ec2.VolumeAttachment,
|
||||
|
||||
// Deletes the EBS disk
|
||||
func (d *awsDisk) deleteVolume() (bool, error) {
|
||||
request := &ec2.DeleteVolumeInput{VolumeId: aws.String(d.awsID)}
|
||||
request := &ec2.DeleteVolumeInput{VolumeId: d.awsID.awsString()}
|
||||
_, err := d.ec2.DeleteVolume(request)
|
||||
if err != nil {
|
||||
if awsError, ok := err.(awserr.Error); ok {
|
||||
@ -1460,7 +1431,7 @@ func (c *Cloud) getAwsInstance(nodeName types.NodeName) (*awsInstance, error) {
|
||||
}
|
||||
|
||||
// AttachDisk implements Volumes.AttachDisk
|
||||
func (c *Cloud) AttachDisk(diskName string, nodeName types.NodeName, readOnly bool) (string, error) {
|
||||
func (c *Cloud) AttachDisk(diskName KubernetesVolumeID, nodeName types.NodeName, readOnly bool) (string, error) {
|
||||
disk, err := newAWSDisk(c, diskName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -1508,7 +1479,7 @@ func (c *Cloud) AttachDisk(diskName string, nodeName types.NodeName, readOnly bo
|
||||
request := &ec2.AttachVolumeInput{
|
||||
Device: aws.String(ec2Device),
|
||||
InstanceId: aws.String(awsInstance.awsID),
|
||||
VolumeId: aws.String(disk.awsID),
|
||||
VolumeId: disk.awsID.awsString(),
|
||||
}
|
||||
|
||||
attachResponse, err := c.ec2.AttachVolume(request)
|
||||
@ -1547,7 +1518,7 @@ func (c *Cloud) AttachDisk(diskName string, nodeName types.NodeName, readOnly bo
|
||||
}
|
||||
|
||||
// DetachDisk implements Volumes.DetachDisk
|
||||
func (c *Cloud) DetachDisk(diskName string, nodeName types.NodeName) (string, error) {
|
||||
func (c *Cloud) DetachDisk(diskName KubernetesVolumeID, nodeName types.NodeName) (string, error) {
|
||||
disk, err := newAWSDisk(c, diskName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -1579,7 +1550,7 @@ func (c *Cloud) DetachDisk(diskName string, nodeName types.NodeName) (string, er
|
||||
|
||||
request := ec2.DetachVolumeInput{
|
||||
InstanceId: &awsInstance.awsID,
|
||||
VolumeId: &disk.awsID,
|
||||
VolumeId: disk.awsID.awsString(),
|
||||
}
|
||||
|
||||
response, err := c.ec2.DetachVolume(&request)
|
||||
@ -1610,7 +1581,7 @@ func (c *Cloud) DetachDisk(diskName string, nodeName types.NodeName) (string, er
|
||||
}
|
||||
|
||||
// CreateDisk implements Volumes.CreateDisk
|
||||
func (c *Cloud) CreateDisk(volumeOptions *VolumeOptions) (string, error) {
|
||||
func (c *Cloud) CreateDisk(volumeOptions *VolumeOptions) (KubernetesVolumeID, error) {
|
||||
allZones, err := c.getAllZones()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error querying for all zones: %v", err)
|
||||
@ -1668,10 +1639,11 @@ func (c *Cloud) CreateDisk(volumeOptions *VolumeOptions) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
az := orEmpty(response.AvailabilityZone)
|
||||
awsID := orEmpty(response.VolumeId)
|
||||
|
||||
volumeName := "aws://" + az + "/" + awsID
|
||||
awsID := awsVolumeID(aws.StringValue(response.VolumeId))
|
||||
if awsID == "" {
|
||||
return "", fmt.Errorf("VolumeID was not returned by CreateVolume")
|
||||
}
|
||||
volumeName := KubernetesVolumeID("aws://" + aws.StringValue(response.AvailabilityZone) + "/" + string(awsID))
|
||||
|
||||
// apply tags
|
||||
tags := make(map[string]string)
|
||||
@ -1684,7 +1656,7 @@ func (c *Cloud) CreateDisk(volumeOptions *VolumeOptions) (string, error) {
|
||||
}
|
||||
|
||||
if len(tags) != 0 {
|
||||
if err := c.createTags(awsID, tags); err != nil {
|
||||
if err := c.createTags(string(awsID), tags); err != nil {
|
||||
// delete the volume and hope it succeeds
|
||||
_, delerr := c.DeleteDisk(volumeName)
|
||||
if delerr != nil {
|
||||
@ -1698,7 +1670,7 @@ func (c *Cloud) CreateDisk(volumeOptions *VolumeOptions) (string, error) {
|
||||
}
|
||||
|
||||
// DeleteDisk implements Volumes.DeleteDisk
|
||||
func (c *Cloud) DeleteDisk(volumeName string) (bool, error) {
|
||||
func (c *Cloud) DeleteDisk(volumeName KubernetesVolumeID) (bool, error) {
|
||||
awsDisk, err := newAWSDisk(c, volumeName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -1707,7 +1679,7 @@ func (c *Cloud) DeleteDisk(volumeName string) (bool, error) {
|
||||
}
|
||||
|
||||
// GetVolumeLabels implements Volumes.GetVolumeLabels
|
||||
func (c *Cloud) GetVolumeLabels(volumeName string) (map[string]string, error) {
|
||||
func (c *Cloud) GetVolumeLabels(volumeName KubernetesVolumeID) (map[string]string, error) {
|
||||
awsDisk, err := newAWSDisk(c, volumeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1733,7 +1705,7 @@ func (c *Cloud) GetVolumeLabels(volumeName string) (map[string]string, error) {
|
||||
}
|
||||
|
||||
// GetDiskPath implements Volumes.GetDiskPath
|
||||
func (c *Cloud) GetDiskPath(volumeName string) (string, error) {
|
||||
func (c *Cloud) GetDiskPath(volumeName KubernetesVolumeID) (string, error) {
|
||||
awsDisk, err := newAWSDisk(c, volumeName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -1749,7 +1721,7 @@ func (c *Cloud) GetDiskPath(volumeName string) (string, error) {
|
||||
}
|
||||
|
||||
// DiskIsAttached implements Volumes.DiskIsAttached
|
||||
func (c *Cloud) DiskIsAttached(diskName string, nodeName types.NodeName) (bool, error) {
|
||||
func (c *Cloud) DiskIsAttached(diskName KubernetesVolumeID, nodeName types.NodeName) (bool, error) {
|
||||
awsInstance, err := c.getAwsInstance(nodeName)
|
||||
if err != nil {
|
||||
if err == cloudprovider.InstanceNotFound {
|
||||
@ -1764,22 +1736,33 @@ func (c *Cloud) DiskIsAttached(diskName string, nodeName types.NodeName) (bool,
|
||||
return false, err
|
||||
}
|
||||
|
||||
diskID, err := diskName.mapToAWSVolumeID()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error mapping volume spec %q to aws id: %v", diskName, err)
|
||||
}
|
||||
|
||||
info, err := awsInstance.describeInstance()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, blockDevice := range info.BlockDeviceMappings {
|
||||
name := aws.StringValue(blockDevice.Ebs.VolumeId)
|
||||
if name == diskName {
|
||||
id := awsVolumeID(aws.StringValue(blockDevice.Ebs.VolumeId))
|
||||
if id == diskID {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *Cloud) DisksAreAttached(diskNames []string, nodeName types.NodeName) (map[string]bool, error) {
|
||||
attached := make(map[string]bool)
|
||||
func (c *Cloud) DisksAreAttached(diskNames []KubernetesVolumeID, nodeName types.NodeName) (map[KubernetesVolumeID]bool, error) {
|
||||
idToDiskName := make(map[awsVolumeID]KubernetesVolumeID)
|
||||
attached := make(map[KubernetesVolumeID]bool)
|
||||
for _, diskName := range diskNames {
|
||||
volumeID, err := diskName.mapToAWSVolumeID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error mapping volume spec %q to aws id: %v", diskName, err)
|
||||
}
|
||||
idToDiskName[volumeID] = diskName
|
||||
attached[diskName] = false
|
||||
}
|
||||
awsInstance, err := c.getAwsInstance(nodeName)
|
||||
@ -1800,12 +1783,11 @@ func (c *Cloud) DisksAreAttached(diskNames []string, nodeName types.NodeName) (m
|
||||
return attached, err
|
||||
}
|
||||
for _, blockDevice := range info.BlockDeviceMappings {
|
||||
volumeId := aws.StringValue(blockDevice.Ebs.VolumeId)
|
||||
for _, diskName := range diskNames {
|
||||
if volumeId == diskName {
|
||||
// Disk is still attached to node
|
||||
attached[diskName] = true
|
||||
}
|
||||
volumeID := awsVolumeID(aws.StringValue(blockDevice.Ebs.VolumeId))
|
||||
diskName, found := idToDiskName[volumeID]
|
||||
if found {
|
||||
// Disk is still attached to node
|
||||
attached[diskName] = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1163,16 +1163,16 @@ func TestGetVolumeLabels(t *testing.T) {
|
||||
awsServices := NewFakeAWSServices()
|
||||
c, err := newAWSCloud(strings.NewReader("[global]"), awsServices)
|
||||
assert.Nil(t, err, "Error building aws cloud: %v", err)
|
||||
volumeId := aws.String("vol-VolumeId")
|
||||
expectedVolumeRequest := &ec2.DescribeVolumesInput{VolumeIds: []*string{volumeId}}
|
||||
volumeId := awsVolumeID("vol-VolumeId")
|
||||
expectedVolumeRequest := &ec2.DescribeVolumesInput{VolumeIds: []*string{volumeId.awsString()}}
|
||||
awsServices.ec2.On("DescribeVolumes", expectedVolumeRequest).Return([]*ec2.Volume{
|
||||
{
|
||||
VolumeId: volumeId,
|
||||
VolumeId: volumeId.awsString(),
|
||||
AvailabilityZone: aws.String("us-east-1a"),
|
||||
},
|
||||
})
|
||||
|
||||
labels, err := c.GetVolumeLabels(*volumeId)
|
||||
labels, err := c.GetVolumeLabels(KubernetesVolumeID("aws:///" + string(volumeId)))
|
||||
|
||||
assert.Nil(t, err, "Error creating Volume %v", err)
|
||||
assert.Equal(t, map[string]string{
|
||||
|
83
pkg/cloudprovider/providers/aws/volumes.go
Normal file
83
pkg/cloudprovider/providers/aws/volumes.go
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License 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 aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// awsVolumeID represents the ID of the volume in the AWS API, e.g. vol-12345678a
|
||||
// The "traditional" format is "vol-12345678"
|
||||
// A new longer format is also being introduced: "vol-12345678abcdef01"
|
||||
// We should not assume anything about the length or format, though it seems
|
||||
// reasonable to assume that volumes will continue to start with "vol-".
|
||||
type awsVolumeID string
|
||||
|
||||
func (i awsVolumeID) awsString() *string {
|
||||
return aws.String(string(i))
|
||||
}
|
||||
|
||||
// KubernetesVolumeID represents the id for a volume in the kubernetes API;
|
||||
// a few forms are recognized:
|
||||
// * aws://<zone>/<awsVolumeId>
|
||||
// * aws:///<awsVolumeId>
|
||||
// * <awsVolumeId>
|
||||
type KubernetesVolumeID string
|
||||
|
||||
// mapToAWSVolumeID extracts the awsVolumeID from the KubernetesVolumeID
|
||||
func (name KubernetesVolumeID) mapToAWSVolumeID() (awsVolumeID, error) {
|
||||
// name looks like aws://availability-zone/awsVolumeId
|
||||
|
||||
// The original idea of the URL-style name was to put the AZ into the
|
||||
// host, so we could find the AZ immediately from the name without
|
||||
// querying the API. But it turns out we don't actually need it for
|
||||
// multi-AZ clusters, as we put the AZ into the labels on the PV instead.
|
||||
// However, if in future we want to support multi-AZ cluster
|
||||
// volume-awareness without using PersistentVolumes, we likely will
|
||||
// want the AZ in the host.
|
||||
|
||||
s := string(name)
|
||||
|
||||
if !strings.HasPrefix(s, "aws://") {
|
||||
// Assume a bare aws volume id (vol-1234...)
|
||||
// Build a URL with an empty host (AZ)
|
||||
s = "aws://" + "" + "/" + s
|
||||
}
|
||||
url, err := url.Parse(s)
|
||||
if err != nil {
|
||||
// TODO: Maybe we should pass a URL into the Volume functions
|
||||
return "", fmt.Errorf("Invalid disk name (%s): %v", name, err)
|
||||
}
|
||||
if url.Scheme != "aws" {
|
||||
return "", fmt.Errorf("Invalid scheme for AWS volume (%s)", name)
|
||||
}
|
||||
|
||||
awsID := url.Path
|
||||
awsID = strings.Trim(awsID, "/")
|
||||
|
||||
// We sanity check the resulting volume; the two known formats are
|
||||
// vol-12345678 and vol-12345678abcdef01
|
||||
// TODO: Regex match?
|
||||
if strings.Contains(awsID, "/") || !strings.HasPrefix(awsID, "vol-") {
|
||||
return "", fmt.Errorf("Invalid format for AWS volume (%s)", name)
|
||||
}
|
||||
|
||||
return awsVolumeID(awsID), nil
|
||||
}
|
@ -64,7 +64,7 @@ func (attacher *awsElasticBlockStoreAttacher) Attach(spec *volume.Spec, nodeName
|
||||
return "", err
|
||||
}
|
||||
|
||||
volumeID := volumeSource.VolumeID
|
||||
volumeID := aws.KubernetesVolumeID(volumeSource.VolumeID)
|
||||
|
||||
// awsCloud.AttachDisk checks if disk is already attached to node and
|
||||
// succeeds in that case, so no need to do that separately.
|
||||
@ -79,8 +79,8 @@ func (attacher *awsElasticBlockStoreAttacher) Attach(spec *volume.Spec, nodeName
|
||||
|
||||
func (attacher *awsElasticBlockStoreAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
|
||||
volumesAttachedCheck := make(map[*volume.Spec]bool)
|
||||
volumeSpecMap := make(map[string]*volume.Spec)
|
||||
volumeIDList := []string{}
|
||||
volumeSpecMap := make(map[aws.KubernetesVolumeID]*volume.Spec)
|
||||
volumeIDList := []aws.KubernetesVolumeID{}
|
||||
for _, spec := range specs {
|
||||
volumeSource, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
@ -88,9 +88,10 @@ func (attacher *awsElasticBlockStoreAttacher) VolumesAreAttached(specs []*volume
|
||||
continue
|
||||
}
|
||||
|
||||
volumeIDList = append(volumeIDList, volumeSource.VolumeID)
|
||||
name := aws.KubernetesVolumeID(volumeSource.VolumeID)
|
||||
volumeIDList = append(volumeIDList, name)
|
||||
volumesAttachedCheck[spec] = true
|
||||
volumeSpecMap[volumeSource.VolumeID] = spec
|
||||
volumeSpecMap[name] = spec
|
||||
}
|
||||
attachedResult, err := attacher.awsVolumes.DisksAreAttached(volumeIDList, nodeName)
|
||||
if err != nil {
|
||||
@ -163,7 +164,7 @@ func (attacher *awsElasticBlockStoreAttacher) GetDeviceMountPath(
|
||||
return "", err
|
||||
}
|
||||
|
||||
return makeGlobalPDPath(attacher.host, volumeSource.VolumeID), nil
|
||||
return makeGlobalPDPath(attacher.host, aws.KubernetesVolumeID(volumeSource.VolumeID)), nil
|
||||
}
|
||||
|
||||
// FIXME: this method can be further pruned.
|
||||
@ -221,7 +222,7 @@ func (plugin *awsElasticBlockStorePlugin) NewDetacher() (volume.Detacher, error)
|
||||
}
|
||||
|
||||
func (detacher *awsElasticBlockStoreDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error {
|
||||
volumeID := path.Base(deviceMountPath)
|
||||
volumeID := aws.KubernetesVolumeID(path.Base(deviceMountPath))
|
||||
|
||||
attached, err := detacher.awsVolumes.DiskIsAttached(volumeID, nodeName)
|
||||
if err != nil {
|
||||
|
@ -29,37 +29,37 @@ import (
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
)
|
||||
|
||||
func TestGetDeviceName_Volume(t *testing.T) {
|
||||
func TestGetVolumeName_Volume(t *testing.T) {
|
||||
plugin := newPlugin()
|
||||
name := "my-aws-volume"
|
||||
name := aws.KubernetesVolumeID("my-aws-volume")
|
||||
spec := createVolSpec(name, false)
|
||||
|
||||
deviceName, err := plugin.GetVolumeName(spec)
|
||||
volumeName, err := plugin.GetVolumeName(spec)
|
||||
if err != nil {
|
||||
t.Errorf("GetDeviceName error: %v", err)
|
||||
t.Errorf("GetVolumeName error: %v", err)
|
||||
}
|
||||
if deviceName != name {
|
||||
t.Errorf("GetDeviceName error: expected %s, got %s", name, deviceName)
|
||||
if volumeName != string(name) {
|
||||
t.Errorf("GetVolumeName error: expected %s, got %s", name, volumeName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDeviceName_PersistentVolume(t *testing.T) {
|
||||
func TestGetVolumeName_PersistentVolume(t *testing.T) {
|
||||
plugin := newPlugin()
|
||||
name := "my-aws-pv"
|
||||
name := aws.KubernetesVolumeID("my-aws-pv")
|
||||
spec := createPVSpec(name, true)
|
||||
|
||||
deviceName, err := plugin.GetVolumeName(spec)
|
||||
volumeName, err := plugin.GetVolumeName(spec)
|
||||
if err != nil {
|
||||
t.Errorf("GetDeviceName error: %v", err)
|
||||
t.Errorf("GetVolumeName error: %v", err)
|
||||
}
|
||||
if deviceName != name {
|
||||
t.Errorf("GetDeviceName error: expected %s, got %s", name, deviceName)
|
||||
if volumeName != string(name) {
|
||||
t.Errorf("GetVolumeName error: expected %s, got %s", name, volumeName)
|
||||
}
|
||||
}
|
||||
|
||||
// One testcase for TestAttachDetach table test below
|
||||
type testcase struct {
|
||||
name string
|
||||
name aws.KubernetesVolumeID
|
||||
// For fake AWS:
|
||||
attach attachCall
|
||||
detach detachCall
|
||||
@ -74,7 +74,7 @@ type testcase struct {
|
||||
}
|
||||
|
||||
func TestAttachDetach(t *testing.T) {
|
||||
diskName := "disk"
|
||||
diskName := aws.KubernetesVolumeID("disk")
|
||||
nodeName := types.NodeName("instance")
|
||||
readOnly := false
|
||||
spec := createVolSpec(diskName, readOnly)
|
||||
@ -111,7 +111,8 @@ func TestAttachDetach(t *testing.T) {
|
||||
detach: detachCall{diskName, nodeName, "/dev/sda", nil},
|
||||
test: func(testcase *testcase) (string, error) {
|
||||
detacher := newDetacher(testcase)
|
||||
return "", detacher.Detach(diskName, nodeName)
|
||||
mountPath := "/mnt/" + string(diskName)
|
||||
return "", detacher.Detach(mountPath, nodeName)
|
||||
},
|
||||
},
|
||||
|
||||
@ -121,7 +122,8 @@ func TestAttachDetach(t *testing.T) {
|
||||
diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, nil},
|
||||
test: func(testcase *testcase) (string, error) {
|
||||
detacher := newDetacher(testcase)
|
||||
return "", detacher.Detach(diskName, nodeName)
|
||||
mountPath := "/mnt/" + string(diskName)
|
||||
return "", detacher.Detach(mountPath, nodeName)
|
||||
},
|
||||
},
|
||||
|
||||
@ -132,7 +134,8 @@ func TestAttachDetach(t *testing.T) {
|
||||
detach: detachCall{diskName, nodeName, "/dev/sda", nil},
|
||||
test: func(testcase *testcase) (string, error) {
|
||||
detacher := newDetacher(testcase)
|
||||
return "", detacher.Detach(diskName, nodeName)
|
||||
mountPath := "/mnt/" + string(diskName)
|
||||
return "", detacher.Detach(mountPath, nodeName)
|
||||
},
|
||||
},
|
||||
|
||||
@ -143,7 +146,8 @@ func TestAttachDetach(t *testing.T) {
|
||||
detach: detachCall{diskName, nodeName, "", detachError},
|
||||
test: func(testcase *testcase) (string, error) {
|
||||
detacher := newDetacher(testcase)
|
||||
return "", detacher.Detach(diskName, nodeName)
|
||||
mountPath := "/mnt/" + string(diskName)
|
||||
return "", detacher.Detach(mountPath, nodeName)
|
||||
},
|
||||
expectedError: detachError,
|
||||
},
|
||||
@ -185,12 +189,12 @@ func newDetacher(testcase *testcase) *awsElasticBlockStoreDetacher {
|
||||
}
|
||||
}
|
||||
|
||||
func createVolSpec(name string, readOnly bool) *volume.Spec {
|
||||
func createVolSpec(name aws.KubernetesVolumeID, readOnly bool) *volume.Spec {
|
||||
return &volume.Spec{
|
||||
Volume: &api.Volume{
|
||||
VolumeSource: api.VolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: name,
|
||||
VolumeID: string(name),
|
||||
ReadOnly: readOnly,
|
||||
},
|
||||
},
|
||||
@ -198,13 +202,13 @@ func createVolSpec(name string, readOnly bool) *volume.Spec {
|
||||
}
|
||||
}
|
||||
|
||||
func createPVSpec(name string, readOnly bool) *volume.Spec {
|
||||
func createPVSpec(name aws.KubernetesVolumeID, readOnly bool) *volume.Spec {
|
||||
return &volume.Spec{
|
||||
PersistentVolume: &api.PersistentVolume{
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: name,
|
||||
VolumeID: string(name),
|
||||
ReadOnly: readOnly,
|
||||
},
|
||||
},
|
||||
@ -216,7 +220,7 @@ func createPVSpec(name string, readOnly bool) *volume.Spec {
|
||||
// Fake AWS implementation
|
||||
|
||||
type attachCall struct {
|
||||
diskName string
|
||||
diskName aws.KubernetesVolumeID
|
||||
nodeName types.NodeName
|
||||
readOnly bool
|
||||
retDeviceName string
|
||||
@ -224,20 +228,20 @@ type attachCall struct {
|
||||
}
|
||||
|
||||
type detachCall struct {
|
||||
diskName string
|
||||
diskName aws.KubernetesVolumeID
|
||||
nodeName types.NodeName
|
||||
retDeviceName string
|
||||
ret error
|
||||
}
|
||||
|
||||
type diskIsAttachedCall struct {
|
||||
diskName string
|
||||
diskName aws.KubernetesVolumeID
|
||||
nodeName types.NodeName
|
||||
isAttached bool
|
||||
ret error
|
||||
}
|
||||
|
||||
func (testcase *testcase) AttachDisk(diskName string, nodeName types.NodeName, readOnly bool) (string, error) {
|
||||
func (testcase *testcase) AttachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName, readOnly bool) (string, error) {
|
||||
expected := &testcase.attach
|
||||
|
||||
if expected.diskName == "" && expected.nodeName == "" {
|
||||
@ -267,7 +271,7 @@ func (testcase *testcase) AttachDisk(diskName string, nodeName types.NodeName, r
|
||||
return expected.retDeviceName, expected.ret
|
||||
}
|
||||
|
||||
func (testcase *testcase) DetachDisk(diskName string, nodeName types.NodeName) (string, error) {
|
||||
func (testcase *testcase) DetachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (string, error) {
|
||||
expected := &testcase.detach
|
||||
|
||||
if expected.diskName == "" && expected.nodeName == "" {
|
||||
@ -292,7 +296,7 @@ func (testcase *testcase) DetachDisk(diskName string, nodeName types.NodeName) (
|
||||
return expected.retDeviceName, expected.ret
|
||||
}
|
||||
|
||||
func (testcase *testcase) DiskIsAttached(diskName string, nodeName types.NodeName) (bool, error) {
|
||||
func (testcase *testcase) DiskIsAttached(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (bool, error) {
|
||||
expected := &testcase.diskIsAttached
|
||||
|
||||
if expected.diskName == "" && expected.nodeName == "" {
|
||||
@ -317,22 +321,22 @@ func (testcase *testcase) DiskIsAttached(diskName string, nodeName types.NodeNam
|
||||
return expected.isAttached, expected.ret
|
||||
}
|
||||
|
||||
func (testcase *testcase) DisksAreAttached(diskNames []string, nodeName types.NodeName) (map[string]bool, error) {
|
||||
func (testcase *testcase) DisksAreAttached(diskNames []aws.KubernetesVolumeID, nodeName types.NodeName) (map[aws.KubernetesVolumeID]bool, error) {
|
||||
return nil, errors.New("Not implemented")
|
||||
}
|
||||
|
||||
func (testcase *testcase) CreateDisk(volumeOptions *aws.VolumeOptions) (volumeName string, err error) {
|
||||
func (testcase *testcase) CreateDisk(volumeOptions *aws.VolumeOptions) (volumeName aws.KubernetesVolumeID, err error) {
|
||||
return "", errors.New("Not implemented")
|
||||
}
|
||||
|
||||
func (testcase *testcase) DeleteDisk(volumeName string) (bool, error) {
|
||||
func (testcase *testcase) DeleteDisk(volumeName aws.KubernetesVolumeID) (bool, error) {
|
||||
return false, errors.New("Not implemented")
|
||||
}
|
||||
|
||||
func (testcase *testcase) GetVolumeLabels(volumeName string) (map[string]string, error) {
|
||||
func (testcase *testcase) GetVolumeLabels(volumeName aws.KubernetesVolumeID) (map[string]string, error) {
|
||||
return map[string]string{}, errors.New("Not implemented")
|
||||
}
|
||||
|
||||
func (testcase *testcase) GetDiskPath(volumeName string) (string, error) {
|
||||
func (testcase *testcase) GetDiskPath(volumeName aws.KubernetesVolumeID) (string, error) {
|
||||
return "", errors.New("Not implemented")
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
@ -102,7 +103,7 @@ func (plugin *awsElasticBlockStorePlugin) newMounterInternal(spec *volume.Spec,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumeID := ebs.VolumeID
|
||||
volumeID := aws.KubernetesVolumeID(ebs.VolumeID)
|
||||
fsType := ebs.FSType
|
||||
partition := ""
|
||||
if ebs.Partition != 0 {
|
||||
@ -153,7 +154,7 @@ func (plugin *awsElasticBlockStorePlugin) newDeleterInternal(spec *volume.Spec,
|
||||
return &awsElasticBlockStoreDeleter{
|
||||
awsElasticBlockStore: &awsElasticBlockStore{
|
||||
volName: spec.Name(),
|
||||
volumeID: spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID,
|
||||
volumeID: aws.KubernetesVolumeID(spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID),
|
||||
manager: manager,
|
||||
plugin: plugin,
|
||||
}}, nil
|
||||
@ -205,7 +206,7 @@ func (plugin *awsElasticBlockStorePlugin) ConstructVolumeSpec(volName, mountPath
|
||||
|
||||
// Abstract interface to PD operations.
|
||||
type ebsManager interface {
|
||||
CreateVolume(provisioner *awsElasticBlockStoreProvisioner) (volumeID string, volumeSizeGB int, labels map[string]string, err error)
|
||||
CreateVolume(provisioner *awsElasticBlockStoreProvisioner) (volumeID aws.KubernetesVolumeID, volumeSizeGB int, labels map[string]string, err error)
|
||||
// Deletes a volume
|
||||
DeleteVolume(deleter *awsElasticBlockStoreDeleter) error
|
||||
}
|
||||
@ -216,7 +217,7 @@ type awsElasticBlockStore struct {
|
||||
volName string
|
||||
podUID types.UID
|
||||
// Unique id of the PD, used to find the disk resource in the provider.
|
||||
volumeID string
|
||||
volumeID aws.KubernetesVolumeID
|
||||
// Specifies the partition to mount
|
||||
partition string
|
||||
// Utility interface that provides API calls to the provider to attach/detach disks.
|
||||
@ -312,9 +313,9 @@ func (b *awsElasticBlockStoreMounter) SetUpAt(dir string, fsGroup *int64) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeGlobalPDPath(host volume.VolumeHost, volumeID string) string {
|
||||
func makeGlobalPDPath(host volume.VolumeHost, volumeID aws.KubernetesVolumeID) string {
|
||||
// Clean up the URI to be more fs-friendly
|
||||
name := volumeID
|
||||
name := string(volumeID)
|
||||
name = strings.Replace(name, "://", "/", -1)
|
||||
return path.Join(host.GetPluginDir(awsElasticBlockStorePluginName), "mounts", name)
|
||||
}
|
||||
@ -432,7 +433,7 @@ func (c *awsElasticBlockStoreProvisioner) Provision() (*api.PersistentVolume, er
|
||||
},
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: volumeID,
|
||||
VolumeID: string(volumeID),
|
||||
FSType: "ext4",
|
||||
Partition: 0,
|
||||
ReadOnly: false,
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
utiltesting "k8s.io/kubernetes/pkg/util/testing"
|
||||
@ -91,7 +92,7 @@ type fakePDManager struct {
|
||||
|
||||
// TODO(jonesdl) To fully test this, we could create a loopback device
|
||||
// and mount that instead.
|
||||
func (fake *fakePDManager) CreateVolume(c *awsElasticBlockStoreProvisioner) (volumeID string, volumeSizeGB int, labels map[string]string, err error) {
|
||||
func (fake *fakePDManager) CreateVolume(c *awsElasticBlockStoreProvisioner) (volumeID aws.KubernetesVolumeID, volumeSizeGB int, labels map[string]string, err error) {
|
||||
labels = make(map[string]string)
|
||||
labels["fakepdmanager"] = "yes"
|
||||
return "test-aws-volume-name", 100, labels, nil
|
||||
|
@ -65,7 +65,7 @@ func (util *AWSDiskUtil) DeleteVolume(d *awsElasticBlockStoreDeleter) error {
|
||||
|
||||
// CreateVolume creates an AWS EBS volume.
|
||||
// Returns: volumeID, volumeSizeGB, labels, error
|
||||
func (util *AWSDiskUtil) CreateVolume(c *awsElasticBlockStoreProvisioner) (string, int, map[string]string, error) {
|
||||
func (util *AWSDiskUtil) CreateVolume(c *awsElasticBlockStoreProvisioner) (aws.KubernetesVolumeID, int, map[string]string, error) {
|
||||
cloud, err := getCloudProvider(c.awsElasticBlockStore.plugin.host.GetCloudProvider())
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
|
@ -242,7 +242,7 @@ func (plugin *gcePersistentDiskPlugin) NewDetacher() (volume.Detacher, error) {
|
||||
// attached to the specified node. If the volume is not attached, it succeeds
|
||||
// (returns nil). If it is attached, Detach issues a call to the GCE cloud
|
||||
// provider to attach it.
|
||||
// Callers are responsible for retryinging on failure.
|
||||
// Callers are responsible for retrying on failure.
|
||||
// Callers are responsible for thread safety between concurrent attach and detach
|
||||
// operations.
|
||||
func (detacher *gcePersistentDiskDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error {
|
||||
|
@ -118,7 +118,8 @@ func (l *persistentVolumeLabel) findAWSEBSLabels(volume *api.PersistentVolume) (
|
||||
|
||||
// TODO: GetVolumeLabels is actually a method on the Volumes interface
|
||||
// If that gets standardized we can refactor to reduce code duplication
|
||||
labels, err := ebsVolumes.GetVolumeLabels(volume.Spec.AWSElasticBlockStore.VolumeID)
|
||||
spec := aws.KubernetesVolumeID(volume.Spec.AWSElasticBlockStore.VolumeID)
|
||||
labels, err := ebsVolumes.GetVolumeLabels(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -34,35 +34,35 @@ type mockVolumes struct {
|
||||
|
||||
var _ aws.Volumes = &mockVolumes{}
|
||||
|
||||
func (v *mockVolumes) AttachDisk(diskName string, nodeName types.NodeName, readOnly bool) (string, error) {
|
||||
func (v *mockVolumes) AttachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName, readOnly bool) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) DetachDisk(diskName string, nodeName types.NodeName) (string, error) {
|
||||
func (v *mockVolumes) DetachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) CreateDisk(volumeOptions *aws.VolumeOptions) (volumeName string, err error) {
|
||||
func (v *mockVolumes) CreateDisk(volumeOptions *aws.VolumeOptions) (volumeName aws.KubernetesVolumeID, err error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) DeleteDisk(volumeName string) (bool, error) {
|
||||
func (v *mockVolumes) DeleteDisk(volumeName aws.KubernetesVolumeID) (bool, error) {
|
||||
return false, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) GetVolumeLabels(volumeName string) (map[string]string, error) {
|
||||
func (v *mockVolumes) GetVolumeLabels(volumeName aws.KubernetesVolumeID) (map[string]string, error) {
|
||||
return v.volumeLabels, v.volumeLabelsError
|
||||
}
|
||||
|
||||
func (c *mockVolumes) GetDiskPath(volumeName string) (string, error) {
|
||||
func (c *mockVolumes) GetDiskPath(volumeName aws.KubernetesVolumeID) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (c *mockVolumes) DiskIsAttached(volumeName string, nodeName types.NodeName) (bool, error) {
|
||||
func (c *mockVolumes) DiskIsAttached(volumeName aws.KubernetesVolumeID, nodeName types.NodeName) (bool, error) {
|
||||
return false, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (c *mockVolumes) DisksAreAttached(diskNames []string, nodeName types.NodeName) (map[string]bool, error) {
|
||||
func (c *mockVolumes) DisksAreAttached(diskNames []aws.KubernetesVolumeID, nodeName types.NodeName) (map[aws.KubernetesVolumeID]bool, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user