volume_zone.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /*
  2. Copyright 2019 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 volumezone
  14. import (
  15. "context"
  16. "fmt"
  17. v1 "k8s.io/api/core/v1"
  18. storage "k8s.io/api/storage/v1"
  19. "k8s.io/apimachinery/pkg/runtime"
  20. corelisters "k8s.io/client-go/listers/core/v1"
  21. storagelisters "k8s.io/client-go/listers/storage/v1"
  22. volumehelpers "k8s.io/cloud-provider/volume/helpers"
  23. "k8s.io/klog"
  24. v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
  25. framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
  26. "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
  27. )
  28. // VolumeZone is a plugin that checks volume zone.
  29. type VolumeZone struct {
  30. pvLister corelisters.PersistentVolumeLister
  31. pvcLister corelisters.PersistentVolumeClaimLister
  32. scLister storagelisters.StorageClassLister
  33. }
  34. var _ framework.FilterPlugin = &VolumeZone{}
  35. const (
  36. // Name is the name of the plugin used in the plugin registry and configurations.
  37. Name = "VolumeZone"
  38. // ErrReasonConflict is used for NoVolumeZoneConflict predicate error.
  39. ErrReasonConflict = "node(s) had no available volume zone"
  40. )
  41. // Name returns name of the plugin. It is used in logs, etc.
  42. func (pl *VolumeZone) Name() string {
  43. return Name
  44. }
  45. // Filter invoked at the filter extension point.
  46. //
  47. // It evaluates if a pod can fit due to the volumes it requests, given
  48. // that some volumes may have zone scheduling constraints. The requirement is that any
  49. // volume zone-labels must match the equivalent zone-labels on the node. It is OK for
  50. // the node to have more zone-label constraints (for example, a hypothetical replicated
  51. // volume might allow region-wide access)
  52. //
  53. // Currently this is only supported with PersistentVolumeClaims, and looks to the labels
  54. // only on the bound PersistentVolume.
  55. //
  56. // Working with volumes declared inline in the pod specification (i.e. not
  57. // using a PersistentVolume) is likely to be harder, as it would require
  58. // determining the zone of a volume during scheduling, and that is likely to
  59. // require calling out to the cloud provider. It seems that we are moving away
  60. // from inline volume declarations anyway.
  61. func (pl *VolumeZone) Filter(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, nodeInfo *nodeinfo.NodeInfo) *framework.Status {
  62. // If a pod doesn't have any volume attached to it, the predicate will always be true.
  63. // Thus we make a fast path for it, to avoid unnecessary computations in this case.
  64. if len(pod.Spec.Volumes) == 0 {
  65. return nil
  66. }
  67. node := nodeInfo.Node()
  68. if node == nil {
  69. return framework.NewStatus(framework.Error, "node not found")
  70. }
  71. nodeConstraints := make(map[string]string)
  72. for k, v := range node.ObjectMeta.Labels {
  73. if k != v1.LabelZoneFailureDomain && k != v1.LabelZoneRegion {
  74. continue
  75. }
  76. nodeConstraints[k] = v
  77. }
  78. if len(nodeConstraints) == 0 {
  79. // The node has no zone constraints, so we're OK to schedule.
  80. // In practice, when using zones, all nodes must be labeled with zone labels.
  81. // We want to fast-path this case though.
  82. return nil
  83. }
  84. for i := range pod.Spec.Volumes {
  85. volume := pod.Spec.Volumes[i]
  86. if volume.PersistentVolumeClaim == nil {
  87. continue
  88. }
  89. pvcName := volume.PersistentVolumeClaim.ClaimName
  90. if pvcName == "" {
  91. return framework.NewStatus(framework.Error, "PersistentVolumeClaim had no name")
  92. }
  93. pvc, err := pl.pvcLister.PersistentVolumeClaims(pod.Namespace).Get(pvcName)
  94. if err != nil {
  95. return framework.NewStatus(framework.Error, err.Error())
  96. }
  97. if pvc == nil {
  98. return framework.NewStatus(framework.Error, fmt.Sprintf("PersistentVolumeClaim was not found: %q", pvcName))
  99. }
  100. pvName := pvc.Spec.VolumeName
  101. if pvName == "" {
  102. scName := v1helper.GetPersistentVolumeClaimClass(pvc)
  103. if len(scName) == 0 {
  104. return framework.NewStatus(framework.Error, fmt.Sprint("PersistentVolumeClaim had no pv name and storageClass name"))
  105. }
  106. class, _ := pl.scLister.Get(scName)
  107. if class == nil {
  108. return framework.NewStatus(framework.Error, fmt.Sprintf("StorageClass %q claimed by PersistentVolumeClaim %q not found", scName, pvcName))
  109. }
  110. if class.VolumeBindingMode == nil {
  111. return framework.NewStatus(framework.Error, fmt.Sprintf("VolumeBindingMode not set for StorageClass %q", scName))
  112. }
  113. if *class.VolumeBindingMode == storage.VolumeBindingWaitForFirstConsumer {
  114. // Skip unbound volumes
  115. continue
  116. }
  117. return framework.NewStatus(framework.Error, fmt.Sprint("PersistentVolume had no name"))
  118. }
  119. pv, err := pl.pvLister.Get(pvName)
  120. if err != nil {
  121. return framework.NewStatus(framework.Error, err.Error())
  122. }
  123. if pv == nil {
  124. return framework.NewStatus(framework.Error, fmt.Sprintf("PersistentVolume was not found: %q", pvName))
  125. }
  126. for k, v := range pv.ObjectMeta.Labels {
  127. if k != v1.LabelZoneFailureDomain && k != v1.LabelZoneRegion {
  128. continue
  129. }
  130. nodeV, _ := nodeConstraints[k]
  131. volumeVSet, err := volumehelpers.LabelZonesToSet(v)
  132. if err != nil {
  133. klog.Warningf("Failed to parse label for %q: %q. Ignoring the label. err=%v. ", k, v, err)
  134. continue
  135. }
  136. if !volumeVSet.Has(nodeV) {
  137. klog.V(10).Infof("Won't schedule pod %q onto node %q due to volume %q (mismatch on %q)", pod.Name, node.Name, pvName, k)
  138. return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonConflict)
  139. }
  140. }
  141. }
  142. return nil
  143. }
  144. // New initializes a new plugin and returns it.
  145. func New(_ *runtime.Unknown, handle framework.FrameworkHandle) (framework.Plugin, error) {
  146. informerFactory := handle.SharedInformerFactory()
  147. pvLister := informerFactory.Core().V1().PersistentVolumes().Lister()
  148. pvcLister := informerFactory.Core().V1().PersistentVolumeClaims().Lister()
  149. scLister := informerFactory.Storage().V1().StorageClasses().Lister()
  150. return &VolumeZone{
  151. pvLister,
  152. pvcLister,
  153. scLister,
  154. }, nil
  155. }