1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package storageos 18 19import ( 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "strings" 25 26 "k8s.io/klog/v2" 27 "k8s.io/mount-utils" 28 utilexec "k8s.io/utils/exec" 29 utilstrings "k8s.io/utils/strings" 30 31 v1 "k8s.io/api/core/v1" 32 "k8s.io/apimachinery/pkg/api/resource" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/types" 35 clientset "k8s.io/client-go/kubernetes" 36 volumehelpers "k8s.io/cloud-provider/volume/helpers" 37 "k8s.io/kubernetes/pkg/volume" 38 "k8s.io/kubernetes/pkg/volume/util" 39) 40 41// ProbeVolumePlugins is the primary entrypoint for volume plugins. 42func ProbeVolumePlugins() []volume.VolumePlugin { 43 return []volume.VolumePlugin{&storageosPlugin{nil}} 44} 45 46type storageosPlugin struct { 47 host volume.VolumeHost 48} 49 50var _ volume.VolumePlugin = &storageosPlugin{} 51var _ volume.PersistentVolumePlugin = &storageosPlugin{} 52var _ volume.DeletableVolumePlugin = &storageosPlugin{} 53var _ volume.ProvisionableVolumePlugin = &storageosPlugin{} 54 55const ( 56 storageosPluginName = "kubernetes.io/storageos" 57 defaultDeviceDir = "/var/lib/storageos/volumes" 58 defaultAPIAddress = "tcp://localhost:5705" 59 defaultAPIUser = "storageos" 60 defaultAPIPassword = "storageos" 61 defaultAPIVersion = "1" 62 defaultFSType = "ext4" 63 defaultNamespace = "default" 64) 65 66func getPath(uid types.UID, volNamespace string, volName string, pvName string, host volume.VolumeHost) string { 67 if len(volNamespace) != 0 && len(volName) != 0 && strings.Count(volName, ".") == 0 { 68 return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(storageosPluginName), pvName+"."+volNamespace+"."+volName) 69 } 70 return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(storageosPluginName), pvName) 71} 72 73func (plugin *storageosPlugin) Init(host volume.VolumeHost) error { 74 plugin.host = host 75 return nil 76} 77 78func (plugin *storageosPlugin) GetPluginName() string { 79 return storageosPluginName 80} 81 82func (plugin *storageosPlugin) GetVolumeName(spec *volume.Spec) (string, error) { 83 volumeSource, _, err := getVolumeSource(spec) 84 if err != nil { 85 return "", err 86 } 87 return fmt.Sprintf("%s/%s", volumeSource.VolumeNamespace, volumeSource.VolumeName), nil 88} 89 90func (plugin *storageosPlugin) CanSupport(spec *volume.Spec) bool { 91 return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.StorageOS != nil) || 92 (spec.Volume != nil && spec.Volume.StorageOS != nil) 93} 94 95func (plugin *storageosPlugin) RequiresRemount(spec *volume.Spec) bool { 96 return false 97} 98 99func (plugin *storageosPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { 100 return []v1.PersistentVolumeAccessMode{ 101 v1.ReadWriteOnce, 102 v1.ReadOnlyMany, 103 } 104} 105 106func (plugin *storageosPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { 107 108 apiCfg, err := getAPICfg(spec, pod, plugin.host.GetKubeClient()) 109 if err != nil { 110 return nil, err 111 } 112 113 return plugin.newMounterInternal(spec, pod, apiCfg, &storageosUtil{host: plugin.host}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName())) 114} 115 116func (plugin *storageosPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, apiCfg *storageosAPIConfig, manager storageosManager, mounter mount.Interface, exec utilexec.Interface) (volume.Mounter, error) { 117 118 volName, volNamespace, fsType, readOnly, err := getVolumeInfoFromSpec(spec) 119 if err != nil { 120 return nil, err 121 } 122 123 return &storageosMounter{ 124 storageos: &storageos{ 125 podUID: pod.UID, 126 podNamespace: pod.GetNamespace(), 127 pvName: spec.Name(), 128 volName: volName, 129 volNamespace: volNamespace, 130 fsType: fsType, 131 readOnly: readOnly, 132 apiCfg: apiCfg, 133 manager: manager, 134 mounter: mounter, 135 exec: exec, 136 plugin: plugin, 137 MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, volNamespace, volName, spec.Name(), plugin.host)), 138 }, 139 diskMounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, 140 mountOptions: util.MountOptionFromSpec(spec), 141 }, nil 142} 143 144func (plugin *storageosPlugin) NewUnmounter(pvName string, podUID types.UID) (volume.Unmounter, error) { 145 return plugin.newUnmounterInternal(pvName, podUID, &storageosUtil{host: plugin.host}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName())) 146} 147 148func (plugin *storageosPlugin) newUnmounterInternal(pvName string, podUID types.UID, manager storageosManager, mounter mount.Interface, exec utilexec.Interface) (volume.Unmounter, error) { 149 150 // Parse volume namespace & name from mountpoint if mounted 151 volNamespace, volName, err := getVolumeInfo(pvName, podUID, plugin.host) 152 if err != nil { 153 return nil, err 154 } 155 156 return &storageosUnmounter{ 157 storageos: &storageos{ 158 podUID: podUID, 159 pvName: pvName, 160 volName: volName, 161 volNamespace: volNamespace, 162 manager: manager, 163 mounter: mounter, 164 exec: exec, 165 plugin: plugin, 166 MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volNamespace, volName, pvName, plugin.host)), 167 }, 168 }, nil 169} 170 171func (plugin *storageosPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) { 172 if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.StorageOS == nil { 173 return nil, fmt.Errorf("spec.PersistentVolumeSource.StorageOS is nil") 174 } 175 176 class, err := util.GetClassForVolume(plugin.host.GetKubeClient(), spec.PersistentVolume) 177 if err != nil { 178 return nil, err 179 } 180 181 var adminSecretName, adminSecretNamespace string 182 183 for k, v := range class.Parameters { 184 switch strings.ToLower(k) { 185 case "adminsecretname": 186 adminSecretName = v 187 case "adminsecretnamespace": 188 adminSecretNamespace = v 189 } 190 } 191 192 apiCfg, err := parsePVSecret(adminSecretNamespace, adminSecretName, plugin.host.GetKubeClient()) 193 if err != nil { 194 return nil, fmt.Errorf("failed to get admin secret from [%q/%q]: %v", adminSecretNamespace, adminSecretName, err) 195 } 196 197 return plugin.newDeleterInternal(spec, apiCfg, &storageosUtil{host: plugin.host}) 198} 199 200func (plugin *storageosPlugin) newDeleterInternal(spec *volume.Spec, apiCfg *storageosAPIConfig, manager storageosManager) (volume.Deleter, error) { 201 202 return &storageosDeleter{ 203 storageosMounter: &storageosMounter{ 204 storageos: &storageos{ 205 pvName: spec.Name(), 206 volName: spec.PersistentVolume.Spec.StorageOS.VolumeName, 207 volNamespace: spec.PersistentVolume.Spec.StorageOS.VolumeNamespace, 208 apiCfg: apiCfg, 209 manager: manager, 210 plugin: plugin, 211 }, 212 }, 213 pvUID: spec.PersistentVolume.UID, 214 }, nil 215} 216 217func (plugin *storageosPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) { 218 return plugin.newProvisionerInternal(options, &storageosUtil{host: plugin.host}) 219} 220 221func (plugin *storageosPlugin) newProvisionerInternal(options volume.VolumeOptions, manager storageosManager) (volume.Provisioner, error) { 222 return &storageosProvisioner{ 223 storageosMounter: &storageosMounter{ 224 storageos: &storageos{ 225 manager: manager, 226 plugin: plugin, 227 }, 228 }, 229 options: options, 230 }, nil 231} 232 233func (plugin *storageosPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { 234 volNamespace, volName, err := getVolumeFromRef(volumeName) 235 if err != nil { 236 volNamespace = defaultNamespace 237 volName = volumeName 238 } 239 storageosVolume := &v1.Volume{ 240 Name: volumeName, 241 VolumeSource: v1.VolumeSource{ 242 StorageOS: &v1.StorageOSVolumeSource{ 243 VolumeName: volName, 244 VolumeNamespace: volNamespace, 245 }, 246 }, 247 } 248 return volume.NewSpecFromVolume(storageosVolume), nil 249} 250 251func (plugin *storageosPlugin) SupportsMountOption() bool { 252 return true 253} 254 255func (plugin *storageosPlugin) SupportsBulkVolumeVerification() bool { 256 return false 257} 258 259func getVolumeSource(spec *volume.Spec) (*v1.StorageOSVolumeSource, bool, error) { 260 if spec.Volume != nil && spec.Volume.StorageOS != nil { 261 return spec.Volume.StorageOS, spec.Volume.StorageOS.ReadOnly, nil 262 } 263 return nil, false, fmt.Errorf("Spec does not reference a StorageOS volume type") 264} 265 266func getPersistentVolumeSource(spec *volume.Spec) (*v1.StorageOSPersistentVolumeSource, bool, error) { 267 if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.StorageOS != nil { 268 return spec.PersistentVolume.Spec.StorageOS, spec.ReadOnly, nil 269 } 270 return nil, false, fmt.Errorf("Spec does not reference a StorageOS persistent volume type") 271} 272 273// storageosManager is the abstract interface to StorageOS volume ops. 274type storageosManager interface { 275 // Connects to the StorageOS API using the supplied configuration. 276 NewAPI(apiCfg *storageosAPIConfig) error 277 // Creates a StorageOS volume. 278 CreateVolume(provisioner *storageosProvisioner) (*storageosVolume, error) 279 // Attaches the disk to the kubelet's host machine. 280 AttachVolume(mounter *storageosMounter) (string, error) 281 // Attaches the device to the host at a mount path. 282 AttachDevice(mounter *storageosMounter, deviceMountPath string) error 283 // Detaches the disk from the kubelet's host machine. 284 DetachVolume(unmounter *storageosUnmounter, dir string) error 285 // Mounts the disk on the Kubelet's host machine. 286 MountVolume(mounter *storageosMounter, mnt, dir string) error 287 // Unmounts the disk from the Kubelet's host machine. 288 UnmountVolume(unounter *storageosUnmounter) error 289 // Deletes the storageos volume. All data will be lost. 290 DeleteVolume(deleter *storageosDeleter) error 291 // Gets the node's device path. 292 DeviceDir(mounter *storageosMounter) string 293} 294 295// storageos volumes represent a bare host directory mount of an StorageOS export. 296type storageos struct { 297 podUID types.UID 298 podNamespace string 299 pvName string 300 volName string 301 volNamespace string 302 readOnly bool 303 description string 304 pool string 305 fsType string 306 sizeGB int 307 labels map[string]string 308 apiCfg *storageosAPIConfig 309 manager storageosManager 310 mounter mount.Interface 311 exec utilexec.Interface 312 plugin *storageosPlugin 313 volume.MetricsProvider 314} 315 316type storageosMounter struct { 317 *storageos 318 319 // The directory containing the StorageOS devices 320 deviceDir string 321 322 // Interface used to mount the file or block device 323 diskMounter *mount.SafeFormatAndMount 324 mountOptions []string 325} 326 327var _ volume.Mounter = &storageosMounter{} 328 329func (b *storageosMounter) GetAttributes() volume.Attributes { 330 return volume.Attributes{ 331 ReadOnly: b.readOnly, 332 Managed: !b.readOnly, 333 SupportsSELinux: true, 334 } 335} 336 337// Checks prior to mount operations to verify that the required components (binaries, etc.) 338// to mount the volume are available on the underlying node. 339// If not, it returns an error 340func (b *storageosMounter) CanMount() error { 341 return nil 342} 343 344// SetUp attaches the disk and bind mounts to the volume path. 345func (b *storageosMounter) SetUp(mounterArgs volume.MounterArgs) error { 346 // Need a namespace to find the volume, try pod's namespace if not set. 347 if b.volNamespace == "" { 348 klog.V(2).Infof("Setting StorageOS volume namespace to pod namespace: %s", b.podNamespace) 349 b.volNamespace = b.podNamespace 350 } 351 352 targetPath := makeGlobalPDName(b.plugin.host, b.pvName, b.volNamespace, b.volName) 353 354 // Attach the device to the host. 355 if err := b.manager.AttachDevice(b, targetPath); err != nil { 356 klog.Errorf("Failed to attach device at %s: %s", targetPath, err.Error()) 357 return err 358 } 359 360 // Attach the StorageOS volume as a block device 361 devicePath, err := b.manager.AttachVolume(b) 362 if err != nil { 363 klog.Errorf("Failed to attach StorageOS volume %s: %s", b.volName, err.Error()) 364 return err 365 } 366 367 // Mount the loop device into the plugin's disk global mount dir. 368 err = b.manager.MountVolume(b, devicePath, targetPath) 369 if err != nil { 370 return err 371 } 372 klog.V(4).Infof("Successfully mounted StorageOS volume %s into global mount directory", b.volName) 373 374 // Bind mount the volume into the pod 375 return b.SetUpAt(b.GetPath(), mounterArgs) 376} 377 378// SetUp bind mounts the disk global mount to the give volume path. 379func (b *storageosMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { 380 notMnt, err := b.mounter.IsLikelyNotMountPoint(dir) 381 klog.V(4).Infof("StorageOS volume set up: %s %v %v", dir, !notMnt, err) 382 if err != nil && !os.IsNotExist(err) { 383 klog.Errorf("Cannot validate mount point: %s %v", dir, err) 384 return err 385 } 386 if !notMnt { 387 return nil 388 } 389 390 if err = os.MkdirAll(dir, 0750); err != nil { 391 klog.Errorf("mkdir failed on disk %s (%v)", dir, err) 392 return err 393 } 394 395 // Perform a bind mount to the full path to allow duplicate mounts of the same PD. 396 options := []string{"bind"} 397 if b.readOnly { 398 options = append(options, "ro") 399 } 400 mountOptions := util.JoinMountOptions(b.mountOptions, options) 401 402 globalPDPath := makeGlobalPDName(b.plugin.host, b.pvName, b.volNamespace, b.volName) 403 klog.V(4).Infof("Attempting to bind mount to pod volume at %s", dir) 404 405 err = b.mounter.MountSensitiveWithoutSystemd(globalPDPath, dir, "", mountOptions, nil) 406 if err != nil { 407 notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) 408 if mntErr != nil { 409 klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) 410 return err 411 } 412 if !notMnt { 413 if mntErr = b.mounter.Unmount(dir); mntErr != nil { 414 klog.Errorf("Failed to unmount: %v", mntErr) 415 return err 416 } 417 notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) 418 if mntErr != nil { 419 klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) 420 return err 421 } 422 if !notMnt { 423 klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir) 424 return err 425 } 426 } 427 os.Remove(dir) 428 klog.Errorf("Mount of disk %s failed: %v", dir, err) 429 return err 430 } 431 432 if !b.readOnly { 433 volume.SetVolumeOwnership(b, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(b.plugin, nil)) 434 } 435 klog.V(4).Infof("StorageOS volume setup complete on %s", dir) 436 return nil 437} 438 439func makeGlobalPDName(host volume.VolumeHost, pvName, volNamespace, volName string) string { 440 return filepath.Join(host.GetPluginDir(utilstrings.EscapeQualifiedName(storageosPluginName)), util.MountsInGlobalPDPath, pvName+"."+volNamespace+"."+volName) 441} 442 443// Given the pod id and PV name, finds the volume's namespace and name from the 444// name or volume mount. We mount as volNamespace.pvName, but k8s will specify 445// only the pvName to unmount. 446// Will return empty volNamespace/pvName if the volume is not mounted. 447func getVolumeInfo(pvName string, podUID types.UID, host volume.VolumeHost) (string, string, error) { 448 if volNamespace, volName, err := getVolumeFromRef(pvName); err == nil { 449 return volNamespace, volName, nil 450 } 451 452 volumeDir := filepath.Dir(host.GetPodVolumeDir(podUID, utilstrings.EscapeQualifiedName(storageosPluginName), pvName)) 453 files, err := ioutil.ReadDir(volumeDir) 454 if err != nil { 455 return "", "", fmt.Errorf("could not read mounts from pod volume dir: %s", err) 456 } 457 for _, f := range files { 458 if f.Mode().IsDir() && strings.HasPrefix(f.Name(), pvName+".") { 459 if volNamespace, volName, err := getVolumeFromRef(f.Name()); err == nil { 460 return volNamespace, volName, nil 461 } 462 } 463 } 464 return "", "", fmt.Errorf("could not get info from unmounted pv %q at %q", pvName, volumeDir) 465} 466 467// Splits the volume ref on "." to return the volNamespace and pvName. Neither 468// namespaces nor service names allow "." in their names. 469func getVolumeFromRef(ref string) (volNamespace string, volName string, err error) { 470 refParts := strings.Split(ref, ".") 471 switch len(refParts) { 472 case 2: 473 return refParts[0], refParts[1], nil 474 case 3: 475 return refParts[1], refParts[2], nil 476 } 477 return "", "", fmt.Errorf("ref not in format volNamespace.volName or pvName.volNamespace.volName") 478} 479 480// GetPath returns the path to the user specific mount of a StorageOS volume 481func (storageosVolume *storageos) GetPath() string { 482 return getPath(storageosVolume.podUID, storageosVolume.volNamespace, storageosVolume.volName, storageosVolume.pvName, storageosVolume.plugin.host) 483} 484 485type storageosUnmounter struct { 486 *storageos 487} 488 489var _ volume.Unmounter = &storageosUnmounter{} 490 491func (b *storageosUnmounter) GetPath() string { 492 return getPath(b.podUID, b.volNamespace, b.volName, b.pvName, b.plugin.host) 493} 494 495// Unmounts the bind mount, and detaches the disk only if the PD 496// resource was the last reference to that disk on the kubelet. 497func (b *storageosUnmounter) TearDown() error { 498 if len(b.volNamespace) == 0 || len(b.volName) == 0 { 499 klog.Warningf("volNamespace: %q, volName: %q not set, skipping TearDown", b.volNamespace, b.volName) 500 return fmt.Errorf("pvName not specified for TearDown, waiting for next sync loop") 501 } 502 // Unmount from pod 503 mountPath := b.GetPath() 504 505 err := b.TearDownAt(mountPath) 506 if err != nil { 507 klog.Errorf("Unmount from pod failed: %v", err) 508 return err 509 } 510 511 // Find device name from global mount 512 globalPDPath := makeGlobalPDName(b.plugin.host, b.pvName, b.volNamespace, b.volName) 513 devicePath, _, err := mount.GetDeviceNameFromMount(b.mounter, globalPDPath) 514 if err != nil { 515 klog.Errorf("Detach failed when getting device from global mount: %v", err) 516 return err 517 } 518 519 // Unmount from plugin's disk global mount dir. 520 err = b.TearDownAt(globalPDPath) 521 if err != nil { 522 klog.Errorf("Detach failed during unmount: %v", err) 523 return err 524 } 525 526 // Detach loop device 527 err = b.manager.DetachVolume(b, devicePath) 528 if err != nil { 529 klog.Errorf("Detach device %s failed for volume %s: %v", devicePath, b.pvName, err) 530 return err 531 } 532 533 klog.V(4).Infof("Successfully unmounted StorageOS volume %s and detached devices", b.pvName) 534 535 return nil 536} 537 538// Unmounts the bind mount, and detaches the disk only if the PD 539// resource was the last reference to that disk on the kubelet. 540func (b *storageosUnmounter) TearDownAt(dir string) error { 541 if err := mount.CleanupMountPoint(dir, b.mounter, false); err != nil { 542 klog.V(4).Infof("Unmounted StorageOS volume %s failed with: %v", b.pvName, err) 543 } 544 if err := b.manager.UnmountVolume(b); err != nil { 545 klog.V(4).Infof("Mount reference for volume %s could not be removed from StorageOS: %v", b.pvName, err) 546 } 547 return nil 548} 549 550type storageosDeleter struct { 551 *storageosMounter 552 pvUID types.UID 553} 554 555var _ volume.Deleter = &storageosDeleter{} 556 557func (d *storageosDeleter) GetPath() string { 558 return getPath(d.podUID, d.volNamespace, d.volName, d.pvName, d.plugin.host) 559} 560 561func (d *storageosDeleter) Delete() error { 562 return d.manager.DeleteVolume(d) 563} 564 565type storageosProvisioner struct { 566 *storageosMounter 567 options volume.VolumeOptions 568} 569 570var _ volume.Provisioner = &storageosProvisioner{} 571 572func (c *storageosProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) { 573 if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) { 574 return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes()) 575 } 576 if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) { 577 return nil, fmt.Errorf("%s does not support block volume provisioning", c.plugin.GetPluginName()) 578 } 579 580 var adminSecretName, adminSecretNamespace string 581 582 // Apply ProvisionerParameters (case-insensitive). We leave validation of 583 // the values to the cloud provider. 584 for k, v := range c.options.Parameters { 585 switch strings.ToLower(k) { 586 case "adminsecretname": 587 adminSecretName = v 588 case "adminsecretnamespace": 589 adminSecretNamespace = v 590 case "volumenamespace": 591 c.volNamespace = v 592 case "description": 593 c.description = v 594 case "pool": 595 c.pool = v 596 case "fstype": 597 c.fsType = v 598 default: 599 return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, c.plugin.GetPluginName()) 600 } 601 } 602 603 // Set from PVC 604 c.podNamespace = c.options.PVC.Namespace 605 c.volName = c.options.PVName 606 if c.volNamespace == "" { 607 c.volNamespace = c.options.PVC.Namespace 608 } 609 c.labels = make(map[string]string) 610 for k, v := range c.options.PVC.Labels { 611 c.labels[k] = v 612 } 613 capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] 614 var err error 615 c.sizeGB, err = volumehelpers.RoundUpToGiBInt(capacity) 616 if err != nil { 617 return nil, err 618 } 619 620 apiCfg, err := parsePVSecret(adminSecretNamespace, adminSecretName, c.plugin.host.GetKubeClient()) 621 if err != nil { 622 return nil, err 623 } 624 c.apiCfg = apiCfg 625 626 vol, err := c.manager.CreateVolume(c) 627 if err != nil { 628 klog.Errorf("failed to create volume: %v", err) 629 return nil, err 630 } 631 if vol.FSType == "" { 632 vol.FSType = defaultFSType 633 } 634 635 pv := &v1.PersistentVolume{ 636 ObjectMeta: metav1.ObjectMeta{ 637 Name: vol.Name, 638 Labels: map[string]string{}, 639 Annotations: map[string]string{ 640 util.VolumeDynamicallyCreatedByKey: "storageos-dynamic-provisioner", 641 }, 642 }, 643 Spec: v1.PersistentVolumeSpec{ 644 PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy, 645 AccessModes: c.options.PVC.Spec.AccessModes, 646 Capacity: v1.ResourceList{ 647 v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", vol.SizeGB)), 648 }, 649 PersistentVolumeSource: v1.PersistentVolumeSource{ 650 StorageOS: &v1.StorageOSPersistentVolumeSource{ 651 VolumeName: vol.Name, 652 VolumeNamespace: vol.Namespace, 653 FSType: vol.FSType, 654 ReadOnly: false, 655 SecretRef: &v1.ObjectReference{ 656 Name: adminSecretName, 657 Namespace: adminSecretNamespace, 658 }, 659 }, 660 }, 661 MountOptions: c.options.MountOptions, 662 }, 663 } 664 if len(c.options.PVC.Spec.AccessModes) == 0 { 665 pv.Spec.AccessModes = c.plugin.GetAccessModes() 666 } 667 if len(vol.Labels) != 0 { 668 if pv.Labels == nil { 669 pv.Labels = make(map[string]string) 670 } 671 for k, v := range vol.Labels { 672 pv.Labels[k] = v 673 } 674 } 675 return pv, nil 676} 677 678// Returns StorageOS volume name, namespace, fstype and readonly from spec 679func getVolumeInfoFromSpec(spec *volume.Spec) (string, string, string, bool, error) { 680 if spec.PersistentVolume != nil { 681 source, readOnly, err := getPersistentVolumeSource(spec) 682 if err != nil { 683 return "", "", "", false, err 684 } 685 return source.VolumeName, source.VolumeNamespace, source.FSType, readOnly, nil 686 } 687 688 if spec.Volume != nil { 689 source, readOnly, err := getVolumeSource(spec) 690 if err != nil { 691 return "", "", "", false, err 692 } 693 return source.VolumeName, source.VolumeNamespace, source.FSType, readOnly, nil 694 } 695 return "", "", "", false, fmt.Errorf("spec not Volume or PersistentVolume") 696} 697 698// Returns API config if secret set, otherwise empty struct so defaults can be 699// attempted. 700func getAPICfg(spec *volume.Spec, pod *v1.Pod, kubeClient clientset.Interface) (*storageosAPIConfig, error) { 701 if spec.PersistentVolume != nil { 702 source, _, err := getPersistentVolumeSource(spec) 703 if err != nil { 704 return nil, err 705 } 706 if source.SecretRef == nil { 707 return nil, nil 708 } 709 return parsePVSecret(source.SecretRef.Namespace, source.SecretRef.Name, kubeClient) 710 } 711 712 if spec.Volume != nil { 713 source, _, err := getVolumeSource(spec) 714 if err != nil { 715 return nil, err 716 } 717 if source.SecretRef == nil { 718 return nil, nil 719 } 720 return parsePodSecret(pod, source.SecretRef.Name, kubeClient) 721 } 722 723 return nil, fmt.Errorf("spec not Volume or PersistentVolume") 724} 725 726func parsePodSecret(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (*storageosAPIConfig, error) { 727 secret, err := util.GetSecretForPod(pod, secretName, kubeClient) 728 if err != nil { 729 klog.Errorf("failed to get secret from [%q/%q]", pod.Namespace, secretName) 730 return nil, fmt.Errorf("failed to get secret from [%q/%q]", pod.Namespace, secretName) 731 } 732 return parseAPIConfig(secret) 733} 734 735// Important: Only to be called with data from a PV to avoid secrets being 736// loaded from a user-suppler namespace. 737func parsePVSecret(namespace, secretName string, kubeClient clientset.Interface) (*storageosAPIConfig, error) { 738 secret, err := util.GetSecretForPV(namespace, secretName, storageosPluginName, kubeClient) 739 if err != nil { 740 klog.Errorf("failed to get secret from [%q/%q]", namespace, secretName) 741 return nil, fmt.Errorf("failed to get secret from [%q/%q]", namespace, secretName) 742 } 743 return parseAPIConfig(secret) 744} 745 746// Parse API configuration from parameters or secret 747func parseAPIConfig(params map[string]string) (*storageosAPIConfig, error) { 748 749 if len(params) == 0 { 750 return nil, fmt.Errorf("empty API config") 751 } 752 753 c := &storageosAPIConfig{} 754 755 for name, data := range params { 756 switch strings.ToLower(name) { 757 case "apiaddress": 758 c.apiAddr = string(data) 759 case "apiusername": 760 c.apiUser = string(data) 761 case "apipassword": 762 c.apiPass = string(data) 763 case "apiversion": 764 c.apiVersion = string(data) 765 } 766 } 767 768 return c, nil 769} 770