util.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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 volume
  14. import (
  15. "fmt"
  16. "reflect"
  17. "time"
  18. "k8s.io/kubernetes/pkg/api"
  19. "k8s.io/kubernetes/pkg/client/cache"
  20. clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
  21. "k8s.io/kubernetes/pkg/fields"
  22. "k8s.io/kubernetes/pkg/runtime"
  23. "k8s.io/kubernetes/pkg/watch"
  24. "hash/fnv"
  25. "math/rand"
  26. "strconv"
  27. "strings"
  28. "github.com/golang/glog"
  29. "k8s.io/kubernetes/pkg/api/errors"
  30. "k8s.io/kubernetes/pkg/api/resource"
  31. "k8s.io/kubernetes/pkg/util/sets"
  32. )
  33. // RecycleVolumeByWatchingPodUntilCompletion is intended for use with volume
  34. // Recyclers. This function will save the given Pod to the API and watch it
  35. // until it completes, fails, or the pod's ActiveDeadlineSeconds is exceeded,
  36. // whichever comes first. An attempt to delete a recycler pod is always
  37. // attempted before returning.
  38. //
  39. // In case there is a pod with the same namespace+name already running, this
  40. // function assumes it's an older instance of the recycler pod and watches
  41. // this old pod instead of starting a new one.
  42. //
  43. // pod - the pod designed by a volume plugin to recycle the volume. pod.Name
  44. // will be overwritten with unique name based on PV.Name.
  45. // client - kube client for API operations.
  46. func RecycleVolumeByWatchingPodUntilCompletion(pvName string, pod *api.Pod, kubeClient clientset.Interface) error {
  47. return internalRecycleVolumeByWatchingPodUntilCompletion(pvName, pod, newRecyclerClient(kubeClient))
  48. }
  49. // same as above func comments, except 'recyclerClient' is a narrower pod API
  50. // interface to ease testing
  51. func internalRecycleVolumeByWatchingPodUntilCompletion(pvName string, pod *api.Pod, recyclerClient recyclerClient) error {
  52. glog.V(5).Infof("creating recycler pod for volume %s\n", pod.Name)
  53. // Generate unique name for the recycler pod - we need to get "already
  54. // exists" error when a previous controller has already started recycling
  55. // the volume. Here we assume that pv.Name is already unique.
  56. pod.Name = "recycler-for-" + pvName
  57. pod.GenerateName = ""
  58. // Start the pod
  59. _, err := recyclerClient.CreatePod(pod)
  60. if err != nil {
  61. if errors.IsAlreadyExists(err) {
  62. glog.V(5).Infof("old recycler pod %q found for volume", pod.Name)
  63. } else {
  64. return fmt.Errorf("Unexpected error creating recycler pod: %+v\n", err)
  65. }
  66. }
  67. defer recyclerClient.DeletePod(pod.Name, pod.Namespace)
  68. // Now only the old pod or the new pod run. Watch it until it finishes.
  69. stopChannel := make(chan struct{})
  70. defer close(stopChannel)
  71. nextPod := recyclerClient.WatchPod(pod.Name, pod.Namespace, stopChannel)
  72. for {
  73. watchedPod := nextPod()
  74. if watchedPod.Status.Phase == api.PodSucceeded {
  75. // volume.Recycle() returns nil on success, else error
  76. return nil
  77. }
  78. if watchedPod.Status.Phase == api.PodFailed {
  79. // volume.Recycle() returns nil on success, else error
  80. if watchedPod.Status.Message != "" {
  81. return fmt.Errorf(watchedPod.Status.Message)
  82. } else {
  83. return fmt.Errorf("pod failed, pod.Status.Message unknown.")
  84. }
  85. }
  86. }
  87. }
  88. // recyclerClient abstracts access to a Pod by providing a narrower interface.
  89. // This makes it easier to mock a client for testing.
  90. type recyclerClient interface {
  91. CreatePod(pod *api.Pod) (*api.Pod, error)
  92. GetPod(name, namespace string) (*api.Pod, error)
  93. DeletePod(name, namespace string) error
  94. WatchPod(name, namespace string, stopChannel chan struct{}) func() *api.Pod
  95. }
  96. func newRecyclerClient(client clientset.Interface) recyclerClient {
  97. return &realRecyclerClient{client}
  98. }
  99. type realRecyclerClient struct {
  100. client clientset.Interface
  101. }
  102. func (c *realRecyclerClient) CreatePod(pod *api.Pod) (*api.Pod, error) {
  103. return c.client.Core().Pods(pod.Namespace).Create(pod)
  104. }
  105. func (c *realRecyclerClient) GetPod(name, namespace string) (*api.Pod, error) {
  106. return c.client.Core().Pods(namespace).Get(name)
  107. }
  108. func (c *realRecyclerClient) DeletePod(name, namespace string) error {
  109. return c.client.Core().Pods(namespace).Delete(name, nil)
  110. }
  111. // WatchPod returns a ListWatch for watching a pod. The stopChannel is used
  112. // to close the reflector backing the watch. The caller is responsible for
  113. // derring a close on the channel to stop the reflector.
  114. func (c *realRecyclerClient) WatchPod(name, namespace string, stopChannel chan struct{}) func() *api.Pod {
  115. fieldSelector, _ := fields.ParseSelector("metadata.name=" + name)
  116. podLW := &cache.ListWatch{
  117. ListFunc: func(options api.ListOptions) (runtime.Object, error) {
  118. options.FieldSelector = fieldSelector
  119. return c.client.Core().Pods(namespace).List(options)
  120. },
  121. WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
  122. options.FieldSelector = fieldSelector
  123. return c.client.Core().Pods(namespace).Watch(options)
  124. },
  125. }
  126. queue := cache.NewFIFO(cache.MetaNamespaceKeyFunc)
  127. cache.NewReflector(podLW, &api.Pod{}, queue, 1*time.Minute).RunUntil(stopChannel)
  128. return func() *api.Pod {
  129. return cache.Pop(queue).(*api.Pod)
  130. }
  131. }
  132. // CalculateTimeoutForVolume calculates time for a Recycler pod to complete a
  133. // recycle operation. The calculation and return value is either the
  134. // minimumTimeout or the timeoutIncrement per Gi of storage size, whichever is
  135. // greater.
  136. func CalculateTimeoutForVolume(minimumTimeout, timeoutIncrement int, pv *api.PersistentVolume) int64 {
  137. giQty := resource.MustParse("1Gi")
  138. pvQty := pv.Spec.Capacity[api.ResourceStorage]
  139. giSize := giQty.Value()
  140. pvSize := pvQty.Value()
  141. timeout := (pvSize / giSize) * int64(timeoutIncrement)
  142. if timeout < int64(minimumTimeout) {
  143. return int64(minimumTimeout)
  144. } else {
  145. return timeout
  146. }
  147. }
  148. // RoundUpSize calculates how many allocation units are needed to accommodate
  149. // a volume of given size. E.g. when user wants 1500MiB volume, while AWS EBS
  150. // allocates volumes in gibibyte-sized chunks,
  151. // RoundUpSize(1500 * 1024*1024, 1024*1024*1024) returns '2'
  152. // (2 GiB is the smallest allocatable volume that can hold 1500MiB)
  153. func RoundUpSize(volumeSizeBytes int64, allocationUnitBytes int64) int64 {
  154. return (volumeSizeBytes + allocationUnitBytes - 1) / allocationUnitBytes
  155. }
  156. // GenerateVolumeName returns a PV name with clusterName prefix. The function
  157. // should be used to generate a name of GCE PD or Cinder volume. It basically
  158. // adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
  159. // string fits given length and cuts "dynamic" if not.
  160. func GenerateVolumeName(clusterName, pvName string, maxLength int) string {
  161. prefix := clusterName + "-dynamic"
  162. pvLen := len(pvName)
  163. // cut the "<clusterName>-dynamic" to fit full pvName into maxLength
  164. // +1 for the '-' dash
  165. if pvLen+1+len(prefix) > maxLength {
  166. prefix = prefix[:maxLength-pvLen-1]
  167. }
  168. return prefix + "-" + pvName
  169. }
  170. // Check if the path from the mounter is empty.
  171. func GetPath(mounter Mounter) (string, error) {
  172. path := mounter.GetPath()
  173. if path == "" {
  174. return "", fmt.Errorf("Path is empty %s", reflect.TypeOf(mounter).String())
  175. }
  176. return path, nil
  177. }
  178. // ChooseZone implements our heuristics for choosing a zone for volume creation based on the volume name
  179. // Volumes are generally round-robin-ed across all active zones, using the hash of the PVC Name.
  180. // However, if the PVCName ends with `-<integer>`, we will hash the prefix, and then add the integer to the hash.
  181. // This means that a PetSet's volumes (`claimname-petsetname-id`) will spread across available zones,
  182. // assuming the id values are consecutive.
  183. func ChooseZoneForVolume(zones sets.String, pvcName string) string {
  184. // We create the volume in a zone determined by the name
  185. // Eventually the scheduler will coordinate placement into an available zone
  186. var hash uint32
  187. var index uint32
  188. if pvcName == "" {
  189. // We should always be called with a name; this shouldn't happen
  190. glog.Warningf("No name defined during volume create; choosing random zone")
  191. hash = rand.Uint32()
  192. } else {
  193. hashString := pvcName
  194. // Heuristic to make sure that volumes in a PetSet are spread across zones
  195. // PetSet PVCs are (currently) named ClaimName-PetSetName-Id,
  196. // where Id is an integer index
  197. lastDash := strings.LastIndexByte(pvcName, '-')
  198. if lastDash != -1 {
  199. petIDString := pvcName[lastDash+1:]
  200. petID, err := strconv.ParseUint(petIDString, 10, 32)
  201. if err == nil {
  202. // Offset by the pet id, so we round-robin across zones
  203. index = uint32(petID)
  204. // We still hash the volume name, but only the base
  205. hashString = pvcName[:lastDash]
  206. glog.V(2).Infof("Detected PetSet-style volume name %q; index=%d", pvcName, index)
  207. }
  208. }
  209. // We hash the (base) volume name, so we don't bias towards the first N zones
  210. h := fnv.New32()
  211. h.Write([]byte(hashString))
  212. hash = h.Sum32()
  213. }
  214. // Zones.List returns zones in a consistent order (sorted)
  215. // We do have a potential failure case where volumes will not be properly spread,
  216. // if the set of zones changes during PetSet volume creation. However, this is
  217. // probably relatively unlikely because we expect the set of zones to be essentially
  218. // static for clusters.
  219. // Hopefully we can address this problem if/when we do full scheduler integration of
  220. // PVC placement (which could also e.g. avoid putting volumes in overloaded or
  221. // unhealthy zones)
  222. zoneSlice := zones.List()
  223. zone := zoneSlice[(hash+index)%uint32(len(zoneSlice))]
  224. glog.V(2).Infof("Creating volume for PVC %q; chose zone=%q from zones=%q", pvcName, zone, zoneSlice)
  225. return zone
  226. }