Skip to content

Commit

Permalink
Don't rely on device name provided by Cinder
Browse files Browse the repository at this point in the history
See issue kubernetes#33128

We can't rely on the device name provided by Cinder, and thus must perform
detection based on the drive serial number (aka It's cinder ID) on the
kubelet itself.

This patch re-works the cinder volume attacher to ignore the supplied
deviceName, and instead defer to the pre-existing GetDevicePath method to
discover the device path based on it's serial number and /dev/disk/by-id
mapping.

This new behavior is controller by a config option, as falling back
to the cinder value when we can't discover a device would risk devices
not showing up, falling back to cinder's guess, and detecting the wrong
disk as attached.
  • Loading branch information
Kiall Mac Innes committed Sep 22, 2016
1 parent b68867f commit 41fa211
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 9 deletions.
20 changes: 19 additions & 1 deletion pkg/cloudprovider/providers/openstack/openstack.go
Expand Up @@ -86,11 +86,16 @@ type LoadBalancerOpts struct {
MonitorMaxRetries uint `gcfg:"monitor-max-retries"`
}

type BlockStorageOpts struct {
TrustDevicePath bool `gcfg:"trust-device-path"` // See Issue #33128
}

// OpenStack is an implementation of cloud provider Interface for OpenStack.
type OpenStack struct {
provider *gophercloud.ProviderClient
region string
lbOpts LoadBalancerOpts
bsOpts BlockStorageOpts
// InstanceID of the server where this OpenStack object is instantiated.
localInstanceID string
}
Expand All @@ -109,6 +114,7 @@ type Config struct {
Region string
}
LoadBalancer LoadBalancerOpts
BlockStorage BlockStorageOpts
}

func init() {
Expand Down Expand Up @@ -145,6 +151,10 @@ func readConfig(config io.Reader) (Config, error) {
}

var cfg Config

// Set default values for config params
cfg.BlockStorage.TrustDevicePath = false

err := gcfg.ReadInto(&cfg, config)
return cfg, err
}
Expand Down Expand Up @@ -186,6 +196,7 @@ func newOpenStack(cfg Config) (*OpenStack, error) {
provider: provider,
region: cfg.Global.Region,
lbOpts: cfg.LoadBalancer,
bsOpts: cfg.BlockStorage,
localInstanceID: id,
}

Expand Down Expand Up @@ -692,8 +703,10 @@ func (os *OpenStack) DeleteVolume(volumeName string) error {
return err
}

// Get device path of attached volume to the compute running kubelet
// Get device path of attached volume to the compute running kubelet, as known by cinder
func (os *OpenStack) GetAttachmentDiskPath(instanceID string, diskName string) (string, error) {
// See issue #33128 - Cinder does not always tell you the right device path, as such
// we must only use this value as a last resort.
disk, err := os.getVolume(diskName)
if err != nil {
return "", err
Expand Down Expand Up @@ -723,3 +736,8 @@ func (os *OpenStack) DiskIsAttached(diskName, instanceID string) (bool, error) {
}
return false, nil
}

// query if we should trust the cinder provide deviceName, See issue #33128
func (os *OpenStack) ShouldTrustDevicePath() bool {
return os.bsOpts.TrustDevicePath
}
5 changes: 5 additions & 0 deletions pkg/cloudprovider/providers/openstack/openstack_test.go
Expand Up @@ -75,6 +75,8 @@ func TestReadConfig(t *testing.T) {
monitor-delay = 1m
monitor-timeout = 30s
monitor-max-retries = 3
[BlockStorage]
trust-device-path = yes
`))
if err != nil {
t.Fatalf("Should succeed when a valid config is provided: %s", err)
Expand All @@ -95,6 +97,9 @@ func TestReadConfig(t *testing.T) {
if cfg.LoadBalancer.MonitorMaxRetries != 3 {
t.Errorf("incorrect lb.monitormaxretries: %d", cfg.LoadBalancer.MonitorMaxRetries)
}
if cfg.BlockStorage.TrustDevicePath != true {
t.Errorf("incorrect bs.trustdevicepath: %v", cfg.BlockStorage.TrustDevicePath)
}
}

func TestToAuthOptions(t *testing.T) {
Expand Down
9 changes: 8 additions & 1 deletion pkg/cloudprovider/providers/rackspace/rackspace.go
Expand Up @@ -613,8 +613,10 @@ func (rs *Rackspace) DetachDisk(instanceID string, partialDiskId string) error {
return nil
}

// Get device path of attached volume to the compute running kubelet
// Get device path of attached volume to the compute running kubelet, as known by cinder
func (rs *Rackspace) GetAttachmentDiskPath(instanceID string, diskName string) (string, error) {
// See issue #33128 - Cinder does not always tell you the right device path, as such
// we must only use this value as a last resort.
disk, err := rs.getVolume(diskName)
if err != nil {
return "", err
Expand Down Expand Up @@ -644,3 +646,8 @@ func (rs *Rackspace) DiskIsAttached(diskName, instanceID string) (bool, error) {
}
return false, nil
}

// query if we should trust the cinder provide deviceName, See issue #33128
func (rs *Rackspace) ShouldTrustDevicePath() bool {
return true
}
31 changes: 24 additions & 7 deletions pkg/volume/cinder/attacher.go
Expand Up @@ -109,17 +109,14 @@ func (attacher *cinderDiskAttacher) Attach(spec *volume.Spec, hostName string) (
}

func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath string, timeout time.Duration) (string, error) {
// NOTE: devicePath is is path as reported by Cinder, which may be incorrect and should not be used. See Issue #33128
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}

volumeID := volumeSource.VolumeID

if devicePath == "" {
return "", fmt.Errorf("WaitForAttach failed for Cinder disk %q: devicePath is empty.", volumeID)
}

ticker := time.NewTicker(checkSleepDuration)
defer ticker.Stop()
timer := time.NewTimer(timeout)
Expand All @@ -131,13 +128,17 @@ func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath
case <-ticker.C:
glog.V(5).Infof("Checking Cinder disk %q is attached.", volumeID)
probeAttachedVolume()
if !attacher.cinderProvider.ShouldTrustDevicePath() {
// Using the Cinder volume ID, find the real device path (See Issue #33128)
devicePath = attacher.cinderProvider.GetDevicePath(volumeID)
}
exists, err := volumeutil.PathExists(devicePath)
if exists && err == nil {
glog.Infof("Successfully found attached Cinder disk %q.", volumeID)
glog.Infof("Successfully found attached Cinder disk %q at %v.", volumeID, devicePath)
return devicePath, nil
} else {
//Log error, if any, and continue checking periodically
glog.Errorf("Error Stat Cinder disk (%q) is attached: %v", volumeID, err)
// Log an error, and continue checking periodically
glog.Errorf("Error could not find attached Cinder disk %q: %v", volumeID, err)
}
case <-timer.C:
return "", fmt.Errorf("Could not find attached Cinder disk %q. Timeout waiting for mount paths to be created.", volumeID)
Expand All @@ -157,6 +158,22 @@ func (attacher *cinderDiskAttacher) GetDeviceMountPath(

// FIXME: this method can be further pruned.
func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
// NOTE: devicePath is is path as reported by Cinder, which may be incorrect and should not be used. See Issue #33128
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return err
}

volumeID := volumeSource.VolumeID

if !attacher.cinderProvider.ShouldTrustDevicePath() {
// Using the Cinder volume ID, find the real device path (See Issue #33128)
devicePath = attacher.cinderProvider.GetDevicePath(volumeID)
if devicePath == "" {
return fmt.Errorf("MountDevice failed for Cinder disk %q: devicePath is empty.", volumeID)
}
}

mounter := attacher.host.GetMounter()
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/volume/cinder/cinder.go
Expand Up @@ -50,6 +50,7 @@ type CinderProvider interface {
InstanceID() (string, error)
GetAttachmentDiskPath(instanceID string, diskName string) (string, error)
DiskIsAttached(diskName, instanceID string) (bool, error)
ShouldTrustDevicePath() bool
Instances() (cloudprovider.Instances, bool)
}

Expand Down

0 comments on commit 41fa211

Please sign in to comment.