aws_ebs.go 19 KB

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