k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/portworx/portworx.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package portworx 18 19 import ( 20 "fmt" 21 "net" 22 "os" 23 "strconv" 24 25 "k8s.io/klog/v2" 26 "k8s.io/mount-utils" 27 utilstrings "k8s.io/utils/strings" 28 29 volumeclient "github.com/libopenstorage/openstorage/api/client/volume" 30 v1 "k8s.io/api/core/v1" 31 "k8s.io/apimachinery/pkg/api/resource" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/types" 34 utilfeature "k8s.io/apiserver/pkg/util/feature" 35 "k8s.io/kubernetes/pkg/features" 36 "k8s.io/kubernetes/pkg/volume" 37 "k8s.io/kubernetes/pkg/volume/util" 38 ) 39 40 const ( 41 attachContextKey = "context" 42 attachHostKey = "host" 43 ) 44 45 // ProbeVolumePlugins is the primary entrypoint for volume plugins. 46 func ProbeVolumePlugins() []volume.VolumePlugin { 47 return []volume.VolumePlugin{&portworxVolumePlugin{nil, nil}} 48 } 49 50 type portworxVolumePlugin struct { 51 host volume.VolumeHost 52 util *portworxVolumeUtil 53 } 54 55 var _ volume.VolumePlugin = &portworxVolumePlugin{} 56 var _ volume.PersistentVolumePlugin = &portworxVolumePlugin{} 57 var _ volume.DeletableVolumePlugin = &portworxVolumePlugin{} 58 var _ volume.ProvisionableVolumePlugin = &portworxVolumePlugin{} 59 var _ volume.ExpandableVolumePlugin = &portworxVolumePlugin{} 60 61 const ( 62 portworxVolumePluginName = "kubernetes.io/portworx-volume" 63 ) 64 65 func getPath(uid types.UID, volName string, host volume.VolumeHost) string { 66 return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(portworxVolumePluginName), volName) 67 } 68 69 func (plugin *portworxVolumePlugin) IsMigratedToCSI() bool { 70 return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx) 71 } 72 73 func (plugin *portworxVolumePlugin) Init(host volume.VolumeHost) error { 74 client, err := volumeclient.NewDriverClient( 75 fmt.Sprintf("http://%s", net.JoinHostPort(host.GetHostName(), strconv.Itoa(osdMgmtDefaultPort))), 76 pxdDriverName, osdDriverVersion, pxDriverName) 77 if err != nil { 78 return err 79 } 80 81 plugin.host = host 82 plugin.util = &portworxVolumeUtil{ 83 portworxClient: client, 84 } 85 86 return nil 87 } 88 89 func (plugin *portworxVolumePlugin) GetPluginName() string { 90 return portworxVolumePluginName 91 } 92 93 func (plugin *portworxVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) { 94 volumeSource, _, err := getVolumeSource(spec) 95 if err != nil { 96 return "", err 97 } 98 99 return volumeSource.VolumeID, nil 100 } 101 102 func (plugin *portworxVolumePlugin) CanSupport(spec *volume.Spec) bool { 103 return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.PortworxVolume != nil) || 104 (spec.Volume != nil && spec.Volume.PortworxVolume != nil) 105 } 106 107 func (plugin *portworxVolumePlugin) RequiresRemount(spec *volume.Spec) bool { 108 return false 109 } 110 111 func (plugin *portworxVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { 112 return []v1.PersistentVolumeAccessMode{ 113 v1.ReadWriteOnce, 114 v1.ReadWriteMany, 115 } 116 } 117 118 func (plugin *portworxVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { 119 return plugin.newMounterInternal(spec, pod.UID, plugin.util, plugin.host.GetMounter(plugin.GetPluginName())) 120 } 121 122 func (plugin *portworxVolumePlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager portworxManager, mounter mount.Interface) (volume.Mounter, error) { 123 pwx, readOnly, err := getVolumeSource(spec) 124 if err != nil { 125 return nil, err 126 } 127 128 volumeID := pwx.VolumeID 129 fsType := pwx.FSType 130 131 return &portworxVolumeMounter{ 132 portworxVolume: &portworxVolume{ 133 podUID: podUID, 134 volName: spec.Name(), 135 volumeID: volumeID, 136 manager: manager, 137 mounter: mounter, 138 plugin: plugin, 139 MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, spec.Name(), plugin.host)), 140 }, 141 fsType: fsType, 142 readOnly: readOnly, 143 diskMounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host)}, nil 144 } 145 146 func (plugin *portworxVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { 147 return plugin.newUnmounterInternal(volName, podUID, plugin.util, plugin.host.GetMounter(plugin.GetPluginName())) 148 } 149 150 func (plugin *portworxVolumePlugin) newUnmounterInternal(volName string, podUID types.UID, manager portworxManager, 151 mounter mount.Interface) (volume.Unmounter, error) { 152 return &portworxVolumeUnmounter{ 153 &portworxVolume{ 154 podUID: podUID, 155 volName: volName, 156 manager: manager, 157 mounter: mounter, 158 plugin: plugin, 159 MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)), 160 }}, nil 161 } 162 163 func (plugin *portworxVolumePlugin) NewDeleter(logger klog.Logger, spec *volume.Spec) (volume.Deleter, error) { 164 return plugin.newDeleterInternal(spec, plugin.util) 165 } 166 167 func (plugin *portworxVolumePlugin) newDeleterInternal(spec *volume.Spec, manager portworxManager) (volume.Deleter, error) { 168 if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.PortworxVolume == nil { 169 return nil, fmt.Errorf("spec.PersistentVolumeSource.PortworxVolume is nil") 170 } 171 172 return &portworxVolumeDeleter{ 173 portworxVolume: &portworxVolume{ 174 volName: spec.Name(), 175 volumeID: spec.PersistentVolume.Spec.PortworxVolume.VolumeID, 176 manager: manager, 177 plugin: plugin, 178 }}, nil 179 } 180 181 func (plugin *portworxVolumePlugin) NewProvisioner(logger klog.Logger, options volume.VolumeOptions) (volume.Provisioner, error) { 182 return plugin.newProvisionerInternal(options, plugin.util) 183 } 184 185 func (plugin *portworxVolumePlugin) newProvisionerInternal(options volume.VolumeOptions, manager portworxManager) (volume.Provisioner, error) { 186 return &portworxVolumeProvisioner{ 187 portworxVolume: &portworxVolume{ 188 manager: manager, 189 plugin: plugin, 190 }, 191 options: options, 192 }, nil 193 } 194 195 func (plugin *portworxVolumePlugin) RequiresFSResize() bool { 196 return false 197 } 198 199 func (plugin *portworxVolumePlugin) ExpandVolumeDevice( 200 spec *volume.Spec, 201 newSize resource.Quantity, 202 oldSize resource.Quantity) (resource.Quantity, error) { 203 klog.V(4).Infof("Expanding: %s from %v to %v", spec.Name(), oldSize, newSize) 204 err := plugin.util.ResizeVolume(spec, newSize, plugin.host) 205 if err != nil { 206 return oldSize, err 207 } 208 209 klog.V(4).Infof("Successfully resized %s to %v", spec.Name(), newSize) 210 return newSize, nil 211 } 212 213 func (plugin *portworxVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { 214 portworxVolume := &v1.Volume{ 215 Name: volumeName, 216 VolumeSource: v1.VolumeSource{ 217 PortworxVolume: &v1.PortworxVolumeSource{ 218 VolumeID: volumeName, 219 }, 220 }, 221 } 222 return volume.ReconstructedVolume{ 223 Spec: volume.NewSpecFromVolume(portworxVolume), 224 }, nil 225 } 226 227 func (plugin *portworxVolumePlugin) SupportsMountOption() bool { 228 return false 229 } 230 231 func (plugin *portworxVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { 232 return false, nil 233 } 234 235 func getVolumeSource( 236 spec *volume.Spec) (*v1.PortworxVolumeSource, bool, error) { 237 if spec.Volume != nil && spec.Volume.PortworxVolume != nil { 238 return spec.Volume.PortworxVolume, spec.Volume.PortworxVolume.ReadOnly, nil 239 } else if spec.PersistentVolume != nil && 240 spec.PersistentVolume.Spec.PortworxVolume != nil { 241 return spec.PersistentVolume.Spec.PortworxVolume, spec.ReadOnly, nil 242 } 243 244 return nil, false, fmt.Errorf("Spec does not reference a Portworx Volume type") 245 } 246 247 // Abstract interface to PD operations. 248 type portworxManager interface { 249 // Creates a volume 250 CreateVolume(provisioner *portworxVolumeProvisioner) (volumeID string, volumeSizeGB int64, labels map[string]string, err error) 251 // Deletes a volume 252 DeleteVolume(deleter *portworxVolumeDeleter) error 253 // Attach a volume 254 AttachVolume(mounter *portworxVolumeMounter, attachOptions map[string]string) (string, error) 255 // Detach a volume 256 DetachVolume(unmounter *portworxVolumeUnmounter) error 257 // Mount a volume 258 MountVolume(mounter *portworxVolumeMounter, mountDir string) error 259 // Unmount a volume 260 UnmountVolume(unmounter *portworxVolumeUnmounter, mountDir string) error 261 // Resize a volume 262 ResizeVolume(spec *volume.Spec, newSize resource.Quantity, host volume.VolumeHost) error 263 } 264 265 // portworxVolume volumes are portworx block devices 266 // that are attached to the kubelet's host machine and exposed to the pod. 267 type portworxVolume struct { 268 volName string 269 podUID types.UID 270 // Unique id of the PD, used to find the disk resource in the provider. 271 volumeID string 272 // Utility interface that provides API calls to the provider to attach/detach disks. 273 manager portworxManager 274 // Mounter interface that provides system calls to mount the global path to the pod local path. 275 mounter mount.Interface 276 plugin *portworxVolumePlugin 277 volume.MetricsProvider 278 } 279 280 type portworxVolumeMounter struct { 281 *portworxVolume 282 // Filesystem type, optional. 283 fsType string 284 // Specifies whether the disk will be attached as read-only. 285 readOnly bool 286 // diskMounter provides the interface that is used to mount the actual block device. 287 diskMounter *mount.SafeFormatAndMount 288 } 289 290 var _ volume.Mounter = &portworxVolumeMounter{} 291 292 func (b *portworxVolumeMounter) GetAttributes() volume.Attributes { 293 return volume.Attributes{ 294 ReadOnly: b.readOnly, 295 Managed: !b.readOnly, 296 SELinuxRelabel: false, 297 } 298 } 299 300 // SetUp attaches the disk and bind mounts to the volume path. 301 func (b *portworxVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error { 302 return b.SetUpAt(b.GetPath(), mounterArgs) 303 } 304 305 // SetUpAt attaches the disk and bind mounts to the volume path. 306 func (b *portworxVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { 307 notMnt, err := b.mounter.IsLikelyNotMountPoint(dir) 308 klog.Infof("Portworx Volume set up. Dir: %s %v %v", dir, !notMnt, err) 309 if err != nil && !os.IsNotExist(err) { 310 klog.Errorf("Cannot validate mountpoint: %s", dir) 311 return err 312 } 313 if !notMnt { 314 return nil 315 } 316 317 attachOptions := make(map[string]string) 318 attachOptions[attachContextKey] = dir 319 attachOptions[attachHostKey] = b.plugin.host.GetHostName() 320 if _, err := b.manager.AttachVolume(b, attachOptions); err != nil { 321 return err 322 } 323 324 klog.V(4).Infof("Portworx Volume %s attached", b.volumeID) 325 326 if err := os.MkdirAll(dir, 0750); err != nil { 327 return err 328 } 329 330 if err := b.manager.MountVolume(b, dir); err != nil { 331 return err 332 } 333 if !b.readOnly { 334 volume.SetVolumeOwnership(b, dir, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(b.plugin, nil)) 335 } 336 klog.Infof("Portworx Volume %s setup at %s", b.volumeID, dir) 337 return nil 338 } 339 340 func (pwx *portworxVolume) GetPath() string { 341 return getPath(pwx.podUID, pwx.volName, pwx.plugin.host) 342 } 343 344 type portworxVolumeUnmounter struct { 345 *portworxVolume 346 } 347 348 var _ volume.Unmounter = &portworxVolumeUnmounter{} 349 350 // Unmounts the bind mount, and detaches the disk only if the PD 351 // resource was the last reference to that disk on the kubelet. 352 func (c *portworxVolumeUnmounter) TearDown() error { 353 return c.TearDownAt(c.GetPath()) 354 } 355 356 // Unmounts the bind mount, and detaches the disk only if the PD 357 // resource was the last reference to that disk on the kubelet. 358 func (c *portworxVolumeUnmounter) TearDownAt(dir string) error { 359 klog.Infof("Portworx Volume TearDown of %s", dir) 360 361 if err := c.manager.UnmountVolume(c, dir); err != nil { 362 return err 363 } 364 365 // Call Portworx Detach Volume. 366 if err := c.manager.DetachVolume(c); err != nil { 367 return err 368 } 369 370 return nil 371 } 372 373 type portworxVolumeDeleter struct { 374 *portworxVolume 375 } 376 377 var _ volume.Deleter = &portworxVolumeDeleter{} 378 379 func (d *portworxVolumeDeleter) GetPath() string { 380 return getPath(d.podUID, d.volName, d.plugin.host) 381 } 382 383 func (d *portworxVolumeDeleter) Delete() error { 384 return d.manager.DeleteVolume(d) 385 } 386 387 type portworxVolumeProvisioner struct { 388 *portworxVolume 389 options volume.VolumeOptions 390 } 391 392 var _ volume.Provisioner = &portworxVolumeProvisioner{} 393 394 func (c *portworxVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) { 395 if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) { 396 return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes()) 397 } 398 399 if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) { 400 return nil, fmt.Errorf("%s does not support block volume provisioning", c.plugin.GetPluginName()) 401 } 402 403 volumeID, sizeGiB, labels, err := c.manager.CreateVolume(c) 404 if err != nil { 405 return nil, err 406 } 407 408 pv := &v1.PersistentVolume{ 409 ObjectMeta: metav1.ObjectMeta{ 410 Name: c.options.PVName, 411 Labels: map[string]string{}, 412 Annotations: map[string]string{ 413 util.VolumeDynamicallyCreatedByKey: "portworx-volume-dynamic-provisioner", 414 }, 415 }, 416 Spec: v1.PersistentVolumeSpec{ 417 PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy, 418 AccessModes: c.options.PVC.Spec.AccessModes, 419 Capacity: v1.ResourceList{ 420 v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGiB)), 421 }, 422 PersistentVolumeSource: v1.PersistentVolumeSource{ 423 PortworxVolume: &v1.PortworxVolumeSource{ 424 VolumeID: volumeID, 425 }, 426 }, 427 }, 428 } 429 430 if len(labels) != 0 { 431 if pv.Labels == nil { 432 pv.Labels = make(map[string]string) 433 } 434 for k, v := range labels { 435 pv.Labels[k] = v 436 } 437 } 438 439 if len(c.options.PVC.Spec.AccessModes) == 0 { 440 pv.Spec.AccessModes = c.plugin.GetAccessModes() 441 } 442 443 return pv, nil 444 }