aws_ebs.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. // +build !providerless
  2. /*
  3. Copyright 2014 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package awsebs
  15. import (
  16. "context"
  17. "fmt"
  18. "os"
  19. "path/filepath"
  20. "regexp"
  21. "runtime"
  22. "strconv"
  23. "strings"
  24. "k8s.io/klog"
  25. "k8s.io/utils/mount"
  26. v1 "k8s.io/api/core/v1"
  27. "k8s.io/apimachinery/pkg/api/resource"
  28. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  29. "k8s.io/apimachinery/pkg/types"
  30. utilfeature "k8s.io/apiserver/pkg/util/feature"
  31. "k8s.io/kubernetes/pkg/features"
  32. "k8s.io/kubernetes/pkg/volume"
  33. "k8s.io/kubernetes/pkg/volume/util"
  34. "k8s.io/legacy-cloud-providers/aws"
  35. utilstrings "k8s.io/utils/strings"
  36. )
  37. // ProbeVolumePlugins is the primary entrypoint for volume plugins.
  38. func ProbeVolumePlugins() []volume.VolumePlugin {
  39. return []volume.VolumePlugin{&awsElasticBlockStorePlugin{nil}}
  40. }
  41. type awsElasticBlockStorePlugin struct {
  42. host volume.VolumeHost
  43. }
  44. var _ volume.VolumePlugin = &awsElasticBlockStorePlugin{}
  45. var _ volume.PersistentVolumePlugin = &awsElasticBlockStorePlugin{}
  46. var _ volume.DeletableVolumePlugin = &awsElasticBlockStorePlugin{}
  47. var _ volume.ProvisionableVolumePlugin = &awsElasticBlockStorePlugin{}
  48. const (
  49. awsElasticBlockStorePluginName = "kubernetes.io/aws-ebs"
  50. awsURLNamePrefix = "aws://"
  51. )
  52. func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
  53. return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(awsElasticBlockStorePluginName), volName)
  54. }
  55. func (plugin *awsElasticBlockStorePlugin) Init(host volume.VolumeHost) error {
  56. plugin.host = host
  57. return nil
  58. }
  59. func (plugin *awsElasticBlockStorePlugin) GetPluginName() string {
  60. return awsElasticBlockStorePluginName
  61. }
  62. func (plugin *awsElasticBlockStorePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
  63. volumeSource, _, err := getVolumeSource(spec)
  64. if err != nil {
  65. return "", err
  66. }
  67. return volumeSource.VolumeID, nil
  68. }
  69. func (plugin *awsElasticBlockStorePlugin) CanSupport(spec *volume.Spec) bool {
  70. return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.AWSElasticBlockStore != nil) ||
  71. (spec.Volume != nil && spec.Volume.AWSElasticBlockStore != nil)
  72. }
  73. func (plugin *awsElasticBlockStorePlugin) RequiresRemount() bool {
  74. return false
  75. }
  76. func (plugin *awsElasticBlockStorePlugin) SupportsMountOption() bool {
  77. return true
  78. }
  79. func (plugin *awsElasticBlockStorePlugin) SupportsBulkVolumeVerification() bool {
  80. return true
  81. }
  82. func (plugin *awsElasticBlockStorePlugin) GetVolumeLimits() (map[string]int64, error) {
  83. volumeLimits := map[string]int64{
  84. util.EBSVolumeLimitKey: util.DefaultMaxEBSVolumes,
  85. }
  86. cloud := plugin.host.GetCloudProvider()
  87. // if we can't fetch cloudprovider we return an error
  88. // hoping external CCM or admin can set it. Returning
  89. // default values from here will mean, no one can
  90. // override them.
  91. if cloud == nil {
  92. return nil, fmt.Errorf("No cloudprovider present")
  93. }
  94. if cloud.ProviderName() != aws.ProviderName {
  95. return nil, fmt.Errorf("Expected aws cloud, found %s", cloud.ProviderName())
  96. }
  97. instances, ok := cloud.Instances()
  98. if !ok {
  99. klog.V(3).Infof("Failed to get instances from cloud provider")
  100. return volumeLimits, nil
  101. }
  102. instanceType, err := instances.InstanceType(context.TODO(), plugin.host.GetNodeName())
  103. if err != nil {
  104. klog.Errorf("Failed to get instance type from AWS cloud provider")
  105. return volumeLimits, nil
  106. }
  107. if ok, _ := regexp.MatchString(util.EBSNitroLimitRegex, instanceType); ok {
  108. volumeLimits[util.EBSVolumeLimitKey] = util.DefaultMaxEBSNitroVolumeLimit
  109. }
  110. return volumeLimits, nil
  111. }
  112. func (plugin *awsElasticBlockStorePlugin) VolumeLimitKey(spec *volume.Spec) string {
  113. return util.EBSVolumeLimitKey
  114. }
  115. func (plugin *awsElasticBlockStorePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
  116. return []v1.PersistentVolumeAccessMode{
  117. v1.ReadWriteOnce,
  118. }
  119. }
  120. func (plugin *awsElasticBlockStorePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
  121. // Inject real implementations here, test through the internal function.
  122. return plugin.newMounterInternal(spec, pod.UID, &AWSDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
  123. }
  124. func (plugin *awsElasticBlockStorePlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager ebsManager, mounter mount.Interface) (volume.Mounter, error) {
  125. // EBSs used directly in a pod have a ReadOnly flag set by the pod author.
  126. // EBSs used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV
  127. ebs, readOnly, err := getVolumeSource(spec)
  128. if err != nil {
  129. return nil, err
  130. }
  131. volumeID := aws.KubernetesVolumeID(ebs.VolumeID)
  132. fsType := ebs.FSType
  133. partition := ""
  134. if ebs.Partition != 0 {
  135. partition = strconv.Itoa(int(ebs.Partition))
  136. }
  137. return &awsElasticBlockStoreMounter{
  138. awsElasticBlockStore: &awsElasticBlockStore{
  139. podUID: podUID,
  140. volName: spec.Name(),
  141. volumeID: volumeID,
  142. partition: partition,
  143. manager: manager,
  144. mounter: mounter,
  145. plugin: plugin,
  146. MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, spec.Name(), plugin.host)),
  147. },
  148. fsType: fsType,
  149. readOnly: readOnly,
  150. diskMounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
  151. mountOptions: util.MountOptionFromSpec(spec),
  152. }, nil
  153. }
  154. func (plugin *awsElasticBlockStorePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
  155. // Inject real implementations here, test through the internal function.
  156. return plugin.newUnmounterInternal(volName, podUID, &AWSDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
  157. }
  158. func (plugin *awsElasticBlockStorePlugin) newUnmounterInternal(volName string, podUID types.UID, manager ebsManager, mounter mount.Interface) (volume.Unmounter, error) {
  159. return &awsElasticBlockStoreUnmounter{&awsElasticBlockStore{
  160. podUID: podUID,
  161. volName: volName,
  162. manager: manager,
  163. mounter: mounter,
  164. plugin: plugin,
  165. MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)),
  166. }}, nil
  167. }
  168. func (plugin *awsElasticBlockStorePlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
  169. return plugin.newDeleterInternal(spec, &AWSDiskUtil{})
  170. }
  171. func (plugin *awsElasticBlockStorePlugin) newDeleterInternal(spec *volume.Spec, manager ebsManager) (volume.Deleter, error) {
  172. if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.AWSElasticBlockStore == nil {
  173. klog.Errorf("spec.PersistentVolumeSource.AWSElasticBlockStore is nil")
  174. return nil, fmt.Errorf("spec.PersistentVolumeSource.AWSElasticBlockStore is nil")
  175. }
  176. return &awsElasticBlockStoreDeleter{
  177. awsElasticBlockStore: &awsElasticBlockStore{
  178. volName: spec.Name(),
  179. volumeID: aws.KubernetesVolumeID(spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID),
  180. manager: manager,
  181. plugin: plugin,
  182. }}, nil
  183. }
  184. func (plugin *awsElasticBlockStorePlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
  185. return plugin.newProvisionerInternal(options, &AWSDiskUtil{})
  186. }
  187. func (plugin *awsElasticBlockStorePlugin) newProvisionerInternal(options volume.VolumeOptions, manager ebsManager) (volume.Provisioner, error) {
  188. return &awsElasticBlockStoreProvisioner{
  189. awsElasticBlockStore: &awsElasticBlockStore{
  190. manager: manager,
  191. plugin: plugin,
  192. },
  193. options: options,
  194. }, nil
  195. }
  196. func getVolumeSource(
  197. spec *volume.Spec) (*v1.AWSElasticBlockStoreVolumeSource, bool, error) {
  198. if spec.Volume != nil && spec.Volume.AWSElasticBlockStore != nil {
  199. return spec.Volume.AWSElasticBlockStore, spec.Volume.AWSElasticBlockStore.ReadOnly, nil
  200. } else if spec.PersistentVolume != nil &&
  201. spec.PersistentVolume.Spec.AWSElasticBlockStore != nil {
  202. return spec.PersistentVolume.Spec.AWSElasticBlockStore, spec.ReadOnly, nil
  203. }
  204. return nil, false, fmt.Errorf("Spec does not reference an AWS EBS volume type")
  205. }
  206. func (plugin *awsElasticBlockStorePlugin) ConstructVolumeSpec(volName, mountPath string) (*volume.Spec, error) {
  207. mounter := plugin.host.GetMounter(plugin.GetPluginName())
  208. kvh, ok := plugin.host.(volume.KubeletVolumeHost)
  209. if !ok {
  210. return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface")
  211. }
  212. hu := kvh.GetHostUtil()
  213. pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName())
  214. volumeID, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir)
  215. if err != nil {
  216. return nil, err
  217. }
  218. volumeID, err = formatVolumeID(volumeID)
  219. if err != nil {
  220. return nil, fmt.Errorf("failed to get AWS volume id from mount path %q: %v", mountPath, err)
  221. }
  222. file := v1.PersistentVolumeFilesystem
  223. return newAWSVolumeSpec(volName, volumeID, file), nil
  224. }
  225. func (plugin *awsElasticBlockStorePlugin) RequiresFSResize() bool {
  226. return true
  227. }
  228. func (plugin *awsElasticBlockStorePlugin) ExpandVolumeDevice(
  229. spec *volume.Spec,
  230. newSize resource.Quantity,
  231. oldSize resource.Quantity) (resource.Quantity, error) {
  232. var awsVolume aws.Volumes
  233. awsVolume, err := getCloudProvider(plugin.host.GetCloudProvider())
  234. if err != nil {
  235. return oldSize, err
  236. }
  237. // we don't expect to receive this call for non PVs
  238. rawVolumeName := spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID
  239. volumeID := aws.KubernetesVolumeID(rawVolumeName)
  240. if volumeID == "" {
  241. return oldSize, fmt.Errorf("EBS.ExpandVolumeDevice Invalid volume id for %s", spec.Name())
  242. }
  243. return awsVolume.ResizeDisk(volumeID, oldSize, newSize)
  244. }
  245. func (plugin *awsElasticBlockStorePlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
  246. fsVolume, err := util.CheckVolumeModeFilesystem(resizeOptions.VolumeSpec)
  247. if err != nil {
  248. return false, fmt.Errorf("error checking VolumeMode: %v", err)
  249. }
  250. // if volume is not a fs file system, there is nothing for us to do here.
  251. if !fsVolume {
  252. return true, nil
  253. }
  254. _, err = util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath)
  255. if err != nil {
  256. return false, err
  257. }
  258. return true, nil
  259. }
  260. var _ volume.NodeExpandableVolumePlugin = &awsElasticBlockStorePlugin{}
  261. var _ volume.ExpandableVolumePlugin = &awsElasticBlockStorePlugin{}
  262. var _ volume.VolumePluginWithAttachLimits = &awsElasticBlockStorePlugin{}
  263. // Abstract interface to PD operations.
  264. type ebsManager interface {
  265. CreateVolume(provisioner *awsElasticBlockStoreProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (volumeID aws.KubernetesVolumeID, volumeSizeGB int, labels map[string]string, fstype string, err error)
  266. // Deletes a volume
  267. DeleteVolume(deleter *awsElasticBlockStoreDeleter) error
  268. }
  269. // awsElasticBlockStore volumes are disk resources provided by Amazon Web Services
  270. // that are attached to the kubelet's host machine and exposed to the pod.
  271. type awsElasticBlockStore struct {
  272. volName string
  273. podUID types.UID
  274. // Unique id of the PD, used to find the disk resource in the provider.
  275. volumeID aws.KubernetesVolumeID
  276. // Specifies the partition to mount
  277. partition string
  278. // Utility interface that provides API calls to the provider to attach/detach disks.
  279. manager ebsManager
  280. // Mounter interface that provides system calls to mount the global path to the pod local path.
  281. mounter mount.Interface
  282. plugin *awsElasticBlockStorePlugin
  283. volume.MetricsProvider
  284. }
  285. type awsElasticBlockStoreMounter struct {
  286. *awsElasticBlockStore
  287. // Filesystem type, optional.
  288. fsType string
  289. // Specifies whether the disk will be attached as read-only.
  290. readOnly bool
  291. // diskMounter provides the interface that is used to mount the actual block device.
  292. diskMounter *mount.SafeFormatAndMount
  293. mountOptions []string
  294. }
  295. var _ volume.Mounter = &awsElasticBlockStoreMounter{}
  296. func (b *awsElasticBlockStoreMounter) GetAttributes() volume.Attributes {
  297. return volume.Attributes{
  298. ReadOnly: b.readOnly,
  299. Managed: !b.readOnly,
  300. SupportsSELinux: true,
  301. }
  302. }
  303. // Checks prior to mount operations to verify that the required components (binaries, etc.)
  304. // to mount the volume are available on the underlying node.
  305. // If not, it returns an error
  306. func (b *awsElasticBlockStoreMounter) CanMount() error {
  307. return nil
  308. }
  309. // SetUp attaches the disk and bind mounts to the volume path.
  310. func (b *awsElasticBlockStoreMounter) SetUp(mounterArgs volume.MounterArgs) error {
  311. return b.SetUpAt(b.GetPath(), mounterArgs)
  312. }
  313. // SetUpAt attaches the disk and bind mounts to the volume path.
  314. func (b *awsElasticBlockStoreMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
  315. // TODO: handle failed mounts here.
  316. notMnt, err := b.mounter.IsLikelyNotMountPoint(dir)
  317. klog.V(4).Infof("PersistentDisk set up: %s %v %v", dir, !notMnt, err)
  318. if err != nil && !os.IsNotExist(err) {
  319. klog.Errorf("cannot validate mount point: %s %v", dir, err)
  320. return err
  321. }
  322. if !notMnt {
  323. return nil
  324. }
  325. globalPDPath := makeGlobalPDPath(b.plugin.host, b.volumeID)
  326. if runtime.GOOS != "windows" {
  327. // On Windows, Mount will create the parent of dir and mklink (create a symbolic link) at dir later, so don't create a
  328. // directory at dir now. Otherwise mklink will error: "Cannot create a file when that file already exists".
  329. // Instead, do nothing. For example when dir is:
  330. // C:\var\lib\kubelet\pods\xxx\volumes\kubernetes.io~aws-ebs\pvc-xxx
  331. // do nothing. Mount will make pvc-xxx a symlink to the global mount path (e.g. C:\var\lib\kubelet\plugins\kubernetes.io\aws-ebs\mounts\aws\us-west-2b\vol-xxx)
  332. if err := os.MkdirAll(dir, 0750); err != nil {
  333. return err
  334. }
  335. }
  336. // Perform a bind mount to the full path to allow duplicate mounts of the same PD.
  337. options := []string{"bind"}
  338. if b.readOnly {
  339. options = append(options, "ro")
  340. }
  341. mountOptions := util.JoinMountOptions(options, b.mountOptions)
  342. err = b.mounter.Mount(globalPDPath, dir, "", mountOptions)
  343. if err != nil {
  344. notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
  345. if mntErr != nil {
  346. klog.Errorf("IsLikelyNotMountPoint check failed for %s: %v", dir, mntErr)
  347. return err
  348. }
  349. if !notMnt {
  350. if mntErr = b.mounter.Unmount(dir); mntErr != nil {
  351. klog.Errorf("failed to unmount %s: %v", dir, mntErr)
  352. return err
  353. }
  354. notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
  355. if mntErr != nil {
  356. klog.Errorf("IsLikelyNotMountPoint check failed for %s: %v", dir, mntErr)
  357. return err
  358. }
  359. if !notMnt {
  360. // This is very odd, we don't expect it. We'll try again next sync loop.
  361. klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir)
  362. return err
  363. }
  364. }
  365. os.Remove(dir)
  366. klog.Errorf("Mount of disk %s failed: %v", dir, err)
  367. return err
  368. }
  369. if !b.readOnly {
  370. volume.SetVolumeOwnership(b, mounterArgs.FsGroup)
  371. }
  372. klog.V(4).Infof("Successfully mounted %s", dir)
  373. return nil
  374. }
  375. func makeGlobalPDPath(host volume.VolumeHost, volumeID aws.KubernetesVolumeID) string {
  376. // Clean up the URI to be more fs-friendly
  377. name := string(volumeID)
  378. name = strings.Replace(name, "://", "/", -1)
  379. return filepath.Join(host.GetPluginDir(awsElasticBlockStorePluginName), util.MountsInGlobalPDPath, name)
  380. }
  381. func (ebs *awsElasticBlockStore) GetPath() string {
  382. return getPath(ebs.podUID, ebs.volName, ebs.plugin.host)
  383. }
  384. type awsElasticBlockStoreUnmounter struct {
  385. *awsElasticBlockStore
  386. }
  387. var _ volume.Unmounter = &awsElasticBlockStoreUnmounter{}
  388. // Unmounts the bind mount, and detaches the disk only if the PD
  389. // resource was the last reference to that disk on the kubelet.
  390. func (c *awsElasticBlockStoreUnmounter) TearDown() error {
  391. return c.TearDownAt(c.GetPath())
  392. }
  393. // Unmounts the bind mount
  394. func (c *awsElasticBlockStoreUnmounter) TearDownAt(dir string) error {
  395. return mount.CleanupMountPoint(dir, c.mounter, false)
  396. }
  397. type awsElasticBlockStoreDeleter struct {
  398. *awsElasticBlockStore
  399. }
  400. var _ volume.Deleter = &awsElasticBlockStoreDeleter{}
  401. func (d *awsElasticBlockStoreDeleter) GetPath() string {
  402. return getPath(d.podUID, d.volName, d.plugin.host)
  403. }
  404. func (d *awsElasticBlockStoreDeleter) Delete() error {
  405. return d.manager.DeleteVolume(d)
  406. }
  407. type awsElasticBlockStoreProvisioner struct {
  408. *awsElasticBlockStore
  409. options volume.VolumeOptions
  410. }
  411. var _ volume.Provisioner = &awsElasticBlockStoreProvisioner{}
  412. func (c *awsElasticBlockStoreProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
  413. if !util.AccessModesContainedInAll(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
  414. return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
  415. }
  416. volumeID, sizeGB, labels, fstype, err := c.manager.CreateVolume(c, selectedNode, allowedTopologies)
  417. if err != nil {
  418. klog.Errorf("Provision failed: %v", err)
  419. return nil, err
  420. }
  421. if fstype == "" {
  422. fstype = "ext4"
  423. }
  424. var volumeMode *v1.PersistentVolumeMode
  425. if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
  426. volumeMode = c.options.PVC.Spec.VolumeMode
  427. if volumeMode != nil && *volumeMode == v1.PersistentVolumeBlock {
  428. // Block volumes should not have any FSType
  429. fstype = ""
  430. }
  431. }
  432. pv := &v1.PersistentVolume{
  433. ObjectMeta: metav1.ObjectMeta{
  434. Name: c.options.PVName,
  435. Labels: map[string]string{},
  436. Annotations: map[string]string{
  437. util.VolumeDynamicallyCreatedByKey: "aws-ebs-dynamic-provisioner",
  438. },
  439. },
  440. Spec: v1.PersistentVolumeSpec{
  441. PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy,
  442. AccessModes: c.options.PVC.Spec.AccessModes,
  443. Capacity: v1.ResourceList{
  444. v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
  445. },
  446. VolumeMode: volumeMode,
  447. PersistentVolumeSource: v1.PersistentVolumeSource{
  448. AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
  449. VolumeID: string(volumeID),
  450. FSType: fstype,
  451. Partition: 0,
  452. ReadOnly: false,
  453. },
  454. },
  455. MountOptions: c.options.MountOptions,
  456. },
  457. }
  458. if len(c.options.PVC.Spec.AccessModes) == 0 {
  459. pv.Spec.AccessModes = c.plugin.GetAccessModes()
  460. }
  461. requirements := make([]v1.NodeSelectorRequirement, 0)
  462. if len(labels) != 0 {
  463. if pv.Labels == nil {
  464. pv.Labels = make(map[string]string)
  465. }
  466. for k, v := range labels {
  467. pv.Labels[k] = v
  468. requirements = append(requirements, v1.NodeSelectorRequirement{Key: k, Operator: v1.NodeSelectorOpIn, Values: []string{v}})
  469. }
  470. }
  471. pv.Spec.NodeAffinity = new(v1.VolumeNodeAffinity)
  472. pv.Spec.NodeAffinity.Required = new(v1.NodeSelector)
  473. pv.Spec.NodeAffinity.Required.NodeSelectorTerms = make([]v1.NodeSelectorTerm, 1)
  474. pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions = requirements
  475. return pv, nil
  476. }