controller.go 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427
  1. /*
  2. Copyright 2016 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 persistentvolume
  14. import (
  15. "fmt"
  16. "reflect"
  17. "time"
  18. "k8s.io/kubernetes/pkg/api"
  19. "k8s.io/kubernetes/pkg/api/unversioned"
  20. "k8s.io/kubernetes/pkg/apis/extensions"
  21. "k8s.io/kubernetes/pkg/client/cache"
  22. clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
  23. "k8s.io/kubernetes/pkg/client/record"
  24. "k8s.io/kubernetes/pkg/cloudprovider"
  25. "k8s.io/kubernetes/pkg/controller/framework"
  26. "k8s.io/kubernetes/pkg/conversion"
  27. "k8s.io/kubernetes/pkg/util/goroutinemap"
  28. vol "k8s.io/kubernetes/pkg/volume"
  29. "github.com/golang/glog"
  30. )
  31. // ==================================================================
  32. // PLEASE DO NOT ATTEMPT TO SIMPLIFY THIS CODE.
  33. // KEEP THE SPACE SHUTTLE FLYING.
  34. // ==================================================================
  35. //
  36. // This controller is intentionally written in a very verbose style. You will
  37. // notice:
  38. //
  39. // 1. Every 'if' statement has a matching 'else' (exception: simple error
  40. // checks for a client API call)
  41. // 2. Things that may seem obvious are commented explicitly
  42. //
  43. // We call this style 'space shuttle style'. Space shuttle style is meant to
  44. // ensure that every branch and condition is considered and accounted for -
  45. // the same way code is written at NASA for applications like the space
  46. // shuttle.
  47. //
  48. // Originally, the work of this controller was split amongst three
  49. // controllers. This controller is the result a large effort to simplify the
  50. // PV subsystem. During that effort, it became clear that we needed to ensure
  51. // that every single condition was handled and accounted for in the code, even
  52. // if it resulted in no-op code branches.
  53. //
  54. // As a result, the controller code may seem overly verbose, commented, and
  55. // 'branchy'. However, a large amount of business knowledge and context is
  56. // recorded here in order to ensure that future maintainers can correctly
  57. // reason through the complexities of the binding behavior. For that reason,
  58. // changes to this file should preserve and add to the space shuttle style.
  59. //
  60. // ==================================================================
  61. // PLEASE DO NOT ATTEMPT TO SIMPLIFY THIS CODE.
  62. // KEEP THE SPACE SHUTTLE FLYING.
  63. // ==================================================================
  64. // Design:
  65. //
  66. // The fundamental key to this design is the bi-directional "pointer" between
  67. // PersistentVolumes (PVs) and PersistentVolumeClaims (PVCs), which is
  68. // represented here as pvc.Spec.VolumeName and pv.Spec.ClaimRef. The bi-
  69. // directionality is complicated to manage in a transactionless system, but
  70. // without it we can't ensure sane behavior in the face of different forms of
  71. // trouble. For example, a rogue HA controller instance could end up racing
  72. // and making multiple bindings that are indistinguishable, resulting in
  73. // potential data loss.
  74. //
  75. // This controller is designed to work in active-passive high availability
  76. // mode. It *could* work also in active-active HA mode, all the object
  77. // transitions are designed to cope with this, however performance could be
  78. // lower as these two active controllers will step on each other toes
  79. // frequently.
  80. //
  81. // This controller supports pre-bound (by the creator) objects in both
  82. // directions: a PVC that wants a specific PV or a PV that is reserved for a
  83. // specific PVC.
  84. //
  85. // The binding is two-step process. PV.Spec.ClaimRef is modified first and
  86. // PVC.Spec.VolumeName second. At any point of this transaction, the PV or PVC
  87. // can be modified by user or other controller or completelly deleted. Also,
  88. // two (or more) controllers may try to bind different volumes to different
  89. // claims at the same time. The controller must recover from any conflicts
  90. // that may arise from these conditions.
  91. // annBindCompleted annotation applies to PVCs. It indicates that the lifecycle
  92. // of the PVC has passed through the initial setup. This information changes how
  93. // we interpret some observations of the state of the objects. Value of this
  94. // annotation does not matter.
  95. const annBindCompleted = "pv.kubernetes.io/bind-completed"
  96. // annBoundByController annotation applies to PVs and PVCs. It indicates that
  97. // the binding (PV->PVC or PVC->PV) was installed by the controller. The
  98. // absence of this annotation means the binding was done by the user (i.e.
  99. // pre-bound). Value of this annotation does not matter.
  100. const annBoundByController = "pv.kubernetes.io/bound-by-controller"
  101. // annClass annotation represents the storage class associated with a resource:
  102. // - in PersistentVolumeClaim it represents required class to match.
  103. // Only PersistentVolumes with the same class (i.e. annotation with the same
  104. // value) can be bound to the claim. In case no such volume exists, the
  105. // controller will provision a new one using StorageClass instance with
  106. // the same name as the annotation value.
  107. // - in PersistentVolume it represents storage class to which the persistent
  108. // volume belongs.
  109. const annClass = "volume.beta.kubernetes.io/storage-class"
  110. // alphaAnnClass annotation represents the previous alpha storage class
  111. // annotation. it's no longer used and held here for posterity.
  112. const annAlphaClass = "volume.alpha.kubernetes.io/storage-class"
  113. // This annotation is added to a PV that has been dynamically provisioned by
  114. // Kubernetes. Its value is name of volume plugin that created the volume.
  115. // It serves both user (to show where a PV comes from) and Kubernetes (to
  116. // recognize dynamically provisioned PVs in its decisions).
  117. const annDynamicallyProvisioned = "pv.kubernetes.io/provisioned-by"
  118. // Name of a tag attached to a real volume in cloud (e.g. AWS EBS or GCE PD)
  119. // with namespace of a persistent volume claim used to create this volume.
  120. const cloudVolumeCreatedForClaimNamespaceTag = "kubernetes.io/created-for/pvc/namespace"
  121. // Name of a tag attached to a real volume in cloud (e.g. AWS EBS or GCE PD)
  122. // with name of a persistent volume claim used to create this volume.
  123. const cloudVolumeCreatedForClaimNameTag = "kubernetes.io/created-for/pvc/name"
  124. // Name of a tag attached to a real volume in cloud (e.g. AWS EBS or GCE PD)
  125. // with name of appropriate Kubernetes persistent volume .
  126. const cloudVolumeCreatedForVolumeNameTag = "kubernetes.io/created-for/pv/name"
  127. // Number of retries when we create a PV object for a provisioned volume.
  128. const createProvisionedPVRetryCount = 5
  129. // Interval between retries when we create a PV object for a provisioned volume.
  130. const createProvisionedPVInterval = 10 * time.Second
  131. // PersistentVolumeController is a controller that synchronizes
  132. // PersistentVolumeClaims and PersistentVolumes. It starts two
  133. // framework.Controllers that watch PersistentVolume and PersistentVolumeClaim
  134. // changes.
  135. type PersistentVolumeController struct {
  136. volumeController *framework.Controller
  137. volumeSource cache.ListerWatcher
  138. claimController *framework.Controller
  139. claimSource cache.ListerWatcher
  140. classReflector *cache.Reflector
  141. classSource cache.ListerWatcher
  142. kubeClient clientset.Interface
  143. eventRecorder record.EventRecorder
  144. cloud cloudprovider.Interface
  145. volumePluginMgr vol.VolumePluginMgr
  146. enableDynamicProvisioning bool
  147. clusterName string
  148. // Cache of the last known version of volumes and claims. This cache is
  149. // thread safe as long as the volumes/claims there are not modified, they
  150. // must be cloned before any modification. These caches get updated both by
  151. // "xxx added/updated/deleted" events from etcd and by the controller when
  152. // it saves newer version to etcd.
  153. volumes persistentVolumeOrderedIndex
  154. claims cache.Store
  155. classes cache.Store
  156. // Map of scheduled/running operations.
  157. runningOperations goroutinemap.GoRoutineMap
  158. // For testing only: hook to call before an asynchronous operation starts.
  159. // Not used when set to nil.
  160. preOperationHook func(operationName string)
  161. createProvisionedPVRetryCount int
  162. createProvisionedPVInterval time.Duration
  163. // Provisioner for annAlphaClass.
  164. // TODO: remove in 1.5
  165. alphaProvisioner vol.ProvisionableVolumePlugin
  166. }
  167. // syncClaim is the main controller method to decide what to do with a claim.
  168. // It's invoked by appropriate framework.Controller callbacks when a claim is
  169. // created, updated or periodically synced. We do not differentiate between
  170. // these events.
  171. // For easier readability, it was split into syncUnboundClaim and syncBoundClaim
  172. // methods.
  173. func (ctrl *PersistentVolumeController) syncClaim(claim *api.PersistentVolumeClaim) error {
  174. glog.V(4).Infof("synchronizing PersistentVolumeClaim[%s]: %s", claimToClaimKey(claim), getClaimStatusForLogging(claim))
  175. if !hasAnnotation(claim.ObjectMeta, annBindCompleted) {
  176. return ctrl.syncUnboundClaim(claim)
  177. } else {
  178. return ctrl.syncBoundClaim(claim)
  179. }
  180. }
  181. // syncUnboundClaim is the main controller method to decide what to do with an
  182. // unbound claim.
  183. func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *api.PersistentVolumeClaim) error {
  184. // This is a new PVC that has not completed binding
  185. // OBSERVATION: pvc is "Pending"
  186. if claim.Spec.VolumeName == "" {
  187. // User did not care which PV they get.
  188. // [Unit test set 1]
  189. volume, err := ctrl.volumes.findBestMatchForClaim(claim)
  190. if err != nil {
  191. glog.V(2).Infof("synchronizing unbound PersistentVolumeClaim[%s]: Error finding PV for claim: %v", claimToClaimKey(claim), err)
  192. return fmt.Errorf("Error finding PV for claim %q: %v", claimToClaimKey(claim), err)
  193. }
  194. if volume == nil {
  195. glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: no volume found", claimToClaimKey(claim))
  196. // No PV could be found
  197. // OBSERVATION: pvc is "Pending", will retry
  198. // TODO: remove Alpha check in 1.5
  199. if getClaimClass(claim) != "" || hasAnnotation(claim.ObjectMeta, annAlphaClass) {
  200. if err = ctrl.provisionClaim(claim); err != nil {
  201. return err
  202. }
  203. return nil
  204. }
  205. // Mark the claim as Pending and try to find a match in the next
  206. // periodic syncClaim
  207. if _, err = ctrl.updateClaimStatus(claim, api.ClaimPending, nil); err != nil {
  208. return err
  209. }
  210. return nil
  211. } else /* pv != nil */ {
  212. // Found a PV for this claim
  213. // OBSERVATION: pvc is "Pending", pv is "Available"
  214. glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume %q found: %s", claimToClaimKey(claim), volume.Name, getVolumeStatusForLogging(volume))
  215. if err = ctrl.bind(volume, claim); err != nil {
  216. // On any error saving the volume or the claim, subsequent
  217. // syncClaim will finish the binding.
  218. return err
  219. }
  220. // OBSERVATION: claim is "Bound", pv is "Bound"
  221. return nil
  222. }
  223. } else /* pvc.Spec.VolumeName != nil */ {
  224. // [Unit test set 2]
  225. // User asked for a specific PV.
  226. glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume %q requested", claimToClaimKey(claim), claim.Spec.VolumeName)
  227. obj, found, err := ctrl.volumes.store.GetByKey(claim.Spec.VolumeName)
  228. if err != nil {
  229. return err
  230. }
  231. if !found {
  232. // User asked for a PV that does not exist.
  233. // OBSERVATION: pvc is "Pending"
  234. // Retry later.
  235. glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume %q requested and not found, will try again next time", claimToClaimKey(claim), claim.Spec.VolumeName)
  236. if _, err = ctrl.updateClaimStatus(claim, api.ClaimPending, nil); err != nil {
  237. return err
  238. }
  239. return nil
  240. } else {
  241. volume, ok := obj.(*api.PersistentVolume)
  242. if !ok {
  243. return fmt.Errorf("Cannot convert object from volume cache to volume %q!?: %+v", claim.Spec.VolumeName, obj)
  244. }
  245. glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume %q requested and found: %s", claimToClaimKey(claim), claim.Spec.VolumeName, getVolumeStatusForLogging(volume))
  246. if volume.Spec.ClaimRef == nil {
  247. // User asked for a PV that is not claimed
  248. // OBSERVATION: pvc is "Pending", pv is "Available"
  249. glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume is unbound, binding", claimToClaimKey(claim))
  250. if err = ctrl.bind(volume, claim); err != nil {
  251. // On any error saving the volume or the claim, subsequent
  252. // syncClaim will finish the binding.
  253. return err
  254. }
  255. // OBSERVATION: pvc is "Bound", pv is "Bound"
  256. return nil
  257. } else if isVolumeBoundToClaim(volume, claim) {
  258. // User asked for a PV that is claimed by this PVC
  259. // OBSERVATION: pvc is "Pending", pv is "Bound"
  260. glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume already bound, finishing the binding", claimToClaimKey(claim))
  261. // Finish the volume binding by adding claim UID.
  262. if err = ctrl.bind(volume, claim); err != nil {
  263. return err
  264. }
  265. // OBSERVATION: pvc is "Bound", pv is "Bound"
  266. return nil
  267. } else {
  268. // User asked for a PV that is claimed by someone else
  269. // OBSERVATION: pvc is "Pending", pv is "Bound"
  270. if !hasAnnotation(claim.ObjectMeta, annBoundByController) {
  271. glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume already bound to different claim by user, will retry later", claimToClaimKey(claim))
  272. // User asked for a specific PV, retry later
  273. if _, err = ctrl.updateClaimStatus(claim, api.ClaimPending, nil); err != nil {
  274. return err
  275. }
  276. return nil
  277. } else {
  278. // This should never happen because someone had to remove
  279. // annBindCompleted annotation on the claim.
  280. glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume already bound to different claim %q by controller, THIS SHOULD NEVER HAPPEN", claimToClaimKey(claim), claimrefToClaimKey(volume.Spec.ClaimRef))
  281. return fmt.Errorf("Invalid binding of claim %q to volume %q: volume already claimed by %q", claimToClaimKey(claim), claim.Spec.VolumeName, claimrefToClaimKey(volume.Spec.ClaimRef))
  282. }
  283. }
  284. }
  285. }
  286. }
  287. // syncBoundClaim is the main controller method to decide what to do with a
  288. // bound claim.
  289. func (ctrl *PersistentVolumeController) syncBoundClaim(claim *api.PersistentVolumeClaim) error {
  290. // hasAnnotation(pvc, annBindCompleted)
  291. // This PVC has previously been bound
  292. // OBSERVATION: pvc is not "Pending"
  293. // [Unit test set 3]
  294. if claim.Spec.VolumeName == "" {
  295. // Claim was bound before but not any more.
  296. if _, err := ctrl.updateClaimStatusWithEvent(claim, api.ClaimLost, nil, api.EventTypeWarning, "ClaimLost", "Bound claim has lost reference to PersistentVolume. Data on the volume is lost!"); err != nil {
  297. return err
  298. }
  299. return nil
  300. }
  301. obj, found, err := ctrl.volumes.store.GetByKey(claim.Spec.VolumeName)
  302. if err != nil {
  303. return err
  304. }
  305. if !found {
  306. // Claim is bound to a non-existing volume.
  307. if _, err = ctrl.updateClaimStatusWithEvent(claim, api.ClaimLost, nil, api.EventTypeWarning, "ClaimLost", "Bound claim has lost its PersistentVolume. Data on the volume is lost!"); err != nil {
  308. return err
  309. }
  310. return nil
  311. } else {
  312. volume, ok := obj.(*api.PersistentVolume)
  313. if !ok {
  314. return fmt.Errorf("Cannot convert object from volume cache to volume %q!?: %#v", claim.Spec.VolumeName, obj)
  315. }
  316. glog.V(4).Infof("synchronizing bound PersistentVolumeClaim[%s]: volume %q found: %s", claimToClaimKey(claim), claim.Spec.VolumeName, getVolumeStatusForLogging(volume))
  317. if volume.Spec.ClaimRef == nil {
  318. // Claim is bound but volume has come unbound.
  319. // Or, a claim was bound and the controller has not received updated
  320. // volume yet. We can't distinguish these cases.
  321. // Bind the volume again and set all states to Bound.
  322. glog.V(4).Infof("synchronizing bound PersistentVolumeClaim[%s]: volume is unbound, fixing", claimToClaimKey(claim))
  323. if err = ctrl.bind(volume, claim); err != nil {
  324. // Objects not saved, next syncPV or syncClaim will try again
  325. return err
  326. }
  327. return nil
  328. } else if volume.Spec.ClaimRef.UID == claim.UID {
  329. // All is well
  330. // NOTE: syncPV can handle this so it can be left out.
  331. // NOTE: bind() call here will do nothing in most cases as
  332. // everything should be already set.
  333. glog.V(4).Infof("synchronizing bound PersistentVolumeClaim[%s]: claim is already correctly bound", claimToClaimKey(claim))
  334. if err = ctrl.bind(volume, claim); err != nil {
  335. // Objects not saved, next syncPV or syncClaim will try again
  336. return err
  337. }
  338. return nil
  339. } else {
  340. // Claim is bound but volume has a different claimant.
  341. // Set the claim phase to 'Lost', which is a terminal
  342. // phase.
  343. if _, err = ctrl.updateClaimStatusWithEvent(claim, api.ClaimLost, nil, api.EventTypeWarning, "ClaimMisbound", "Two claims are bound to the same volume, this one is bound incorrectly"); err != nil {
  344. return err
  345. }
  346. return nil
  347. }
  348. }
  349. }
  350. // syncVolume is the main controller method to decide what to do with a volume.
  351. // It's invoked by appropriate framework.Controller callbacks when a volume is
  352. // created, updated or periodically synced. We do not differentiate between
  353. // these events.
  354. func (ctrl *PersistentVolumeController) syncVolume(volume *api.PersistentVolume) error {
  355. glog.V(4).Infof("synchronizing PersistentVolume[%s]: %s", volume.Name, getVolumeStatusForLogging(volume))
  356. // [Unit test set 4]
  357. if volume.Spec.ClaimRef == nil {
  358. // Volume is unused
  359. glog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is unused", volume.Name)
  360. if _, err := ctrl.updateVolumePhase(volume, api.VolumeAvailable, ""); err != nil {
  361. // Nothing was saved; we will fall back into the same
  362. // condition in the next call to this method
  363. return err
  364. }
  365. return nil
  366. } else /* pv.Spec.ClaimRef != nil */ {
  367. // Volume is bound to a claim.
  368. if volume.Spec.ClaimRef.UID == "" {
  369. // The PV is reserved for a PVC; that PVC has not yet been
  370. // bound to this PV; the PVC sync will handle it.
  371. glog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is pre-bound to claim %s", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
  372. if _, err := ctrl.updateVolumePhase(volume, api.VolumeAvailable, ""); err != nil {
  373. // Nothing was saved; we will fall back into the same
  374. // condition in the next call to this method
  375. return err
  376. }
  377. return nil
  378. }
  379. glog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is bound to claim %s", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
  380. // Get the PVC by _name_
  381. var claim *api.PersistentVolumeClaim
  382. claimName := claimrefToClaimKey(volume.Spec.ClaimRef)
  383. obj, found, err := ctrl.claims.GetByKey(claimName)
  384. if err != nil {
  385. return err
  386. }
  387. if !found {
  388. glog.V(4).Infof("synchronizing PersistentVolume[%s]: claim %s not found", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
  389. // Fall through with claim = nil
  390. } else {
  391. var ok bool
  392. claim, ok = obj.(*api.PersistentVolumeClaim)
  393. if !ok {
  394. return fmt.Errorf("Cannot convert object from volume cache to volume %q!?: %#v", claim.Spec.VolumeName, obj)
  395. }
  396. glog.V(4).Infof("synchronizing PersistentVolume[%s]: claim %s found: %s", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef), getClaimStatusForLogging(claim))
  397. }
  398. if claim != nil && claim.UID != volume.Spec.ClaimRef.UID {
  399. // The claim that the PV was pointing to was deleted, and another
  400. // with the same name created.
  401. glog.V(4).Infof("synchronizing PersistentVolume[%s]: claim %s has different UID, the old one must have been deleted", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
  402. // Treat the volume as bound to a missing claim.
  403. claim = nil
  404. }
  405. if claim == nil {
  406. // If we get into this block, the claim must have been deleted;
  407. // NOTE: reclaimVolume may either release the PV back into the pool or
  408. // recycle it or do nothing (retain)
  409. // Do not overwrite previous Failed state - let the user see that
  410. // something went wrong, while we still re-try to reclaim the
  411. // volume.
  412. if volume.Status.Phase != api.VolumeReleased && volume.Status.Phase != api.VolumeFailed {
  413. // Also, log this only once:
  414. glog.V(2).Infof("volume %q is released and reclaim policy %q will be executed", volume.Name, volume.Spec.PersistentVolumeReclaimPolicy)
  415. if volume, err = ctrl.updateVolumePhase(volume, api.VolumeReleased, ""); err != nil {
  416. // Nothing was saved; we will fall back into the same condition
  417. // in the next call to this method
  418. return err
  419. }
  420. }
  421. if err = ctrl.reclaimVolume(volume); err != nil {
  422. // Release failed, we will fall back into the same condition
  423. // in the next call to this method
  424. return err
  425. }
  426. return nil
  427. } else if claim.Spec.VolumeName == "" {
  428. if hasAnnotation(volume.ObjectMeta, annBoundByController) {
  429. // The binding is not completed; let PVC sync handle it
  430. glog.V(4).Infof("synchronizing PersistentVolume[%s]: volume not bound yet, waiting for syncClaim to fix it", volume.Name)
  431. } else {
  432. // Dangling PV; try to re-establish the link in the PVC sync
  433. glog.V(4).Infof("synchronizing PersistentVolume[%s]: volume was bound and got unbound (by user?), waiting for syncClaim to fix it", volume.Name)
  434. }
  435. // In both cases, the volume is Bound and the claim is Pending.
  436. // Next syncClaim will fix it. To speed it up, we enqueue the claim
  437. // into the controller, which results in syncClaim to be called
  438. // shortly (and in the right goroutine).
  439. // This speeds up binding of provisioned volumes - provisioner saves
  440. // only the new PV and it expects that next syncClaim will bind the
  441. // claim to it.
  442. clone, err := conversion.NewCloner().DeepCopy(claim)
  443. if err != nil {
  444. return fmt.Errorf("error cloning claim %q: %v", claimToClaimKey(claim), err)
  445. }
  446. glog.V(5).Infof("requeueing claim %q for faster syncClaim", claimToClaimKey(claim))
  447. err = ctrl.claimController.Requeue(clone)
  448. if err != nil {
  449. return fmt.Errorf("error enqueing claim %q for faster sync: %v", claimToClaimKey(claim), err)
  450. }
  451. return nil
  452. } else if claim.Spec.VolumeName == volume.Name {
  453. // Volume is bound to a claim properly, update status if necessary
  454. glog.V(4).Infof("synchronizing PersistentVolume[%s]: all is bound", volume.Name)
  455. if _, err = ctrl.updateVolumePhase(volume, api.VolumeBound, ""); err != nil {
  456. // Nothing was saved; we will fall back into the same
  457. // condition in the next call to this method
  458. return err
  459. }
  460. return nil
  461. } else {
  462. // Volume is bound to a claim, but the claim is bound elsewhere
  463. if hasAnnotation(volume.ObjectMeta, annDynamicallyProvisioned) && volume.Spec.PersistentVolumeReclaimPolicy == api.PersistentVolumeReclaimDelete {
  464. // This volume was dynamically provisioned for this claim. The
  465. // claim got bound elsewhere, and thus this volume is not
  466. // needed. Delete it.
  467. if err = ctrl.reclaimVolume(volume); err != nil {
  468. // Deletion failed, we will fall back into the same condition
  469. // in the next call to this method
  470. return err
  471. }
  472. return nil
  473. } else {
  474. // Volume is bound to a claim, but the claim is bound elsewhere
  475. // and it's not dynamically provisioned.
  476. if hasAnnotation(volume.ObjectMeta, annBoundByController) {
  477. // This is part of the normal operation of the controller; the
  478. // controller tried to use this volume for a claim but the claim
  479. // was fulfilled by another volume. We did this; fix it.
  480. glog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is bound by controller to a claim that is bound to another volume, unbinding", volume.Name)
  481. if err = ctrl.unbindVolume(volume); err != nil {
  482. return err
  483. }
  484. return nil
  485. } else {
  486. // The PV must have been created with this ptr; leave it alone.
  487. glog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is bound by user to a claim that is bound to another volume, waiting for the claim to get unbound", volume.Name)
  488. // This just updates the volume phase and clears
  489. // volume.Spec.ClaimRef.UID. It leaves the volume pre-bound
  490. // to the claim.
  491. if err = ctrl.unbindVolume(volume); err != nil {
  492. return err
  493. }
  494. return nil
  495. }
  496. }
  497. }
  498. }
  499. }
  500. // updateClaimStatus saves new claim.Status to API server.
  501. // Parameters:
  502. // claim - claim to update
  503. // phasephase - phase to set
  504. // volume - volume which Capacity is set into claim.Status.Capacity
  505. func (ctrl *PersistentVolumeController) updateClaimStatus(claim *api.PersistentVolumeClaim, phase api.PersistentVolumeClaimPhase, volume *api.PersistentVolume) (*api.PersistentVolumeClaim, error) {
  506. glog.V(4).Infof("updating PersistentVolumeClaim[%s] status: set phase %s", claimToClaimKey(claim), phase)
  507. dirty := false
  508. clone, err := conversion.NewCloner().DeepCopy(claim)
  509. if err != nil {
  510. return nil, fmt.Errorf("Error cloning claim: %v", err)
  511. }
  512. claimClone, ok := clone.(*api.PersistentVolumeClaim)
  513. if !ok {
  514. return nil, fmt.Errorf("Unexpected claim cast error : %v", claimClone)
  515. }
  516. if claim.Status.Phase != phase {
  517. claimClone.Status.Phase = phase
  518. dirty = true
  519. }
  520. if volume == nil {
  521. // Need to reset AccessModes and Capacity
  522. if claim.Status.AccessModes != nil {
  523. claimClone.Status.AccessModes = nil
  524. dirty = true
  525. }
  526. if claim.Status.Capacity != nil {
  527. claimClone.Status.Capacity = nil
  528. dirty = true
  529. }
  530. } else {
  531. // Need to update AccessModes and Capacity
  532. if !reflect.DeepEqual(claim.Status.AccessModes, volume.Spec.AccessModes) {
  533. claimClone.Status.AccessModes = volume.Spec.AccessModes
  534. dirty = true
  535. }
  536. volumeCap, ok := volume.Spec.Capacity[api.ResourceStorage]
  537. if !ok {
  538. return nil, fmt.Errorf("PersistentVolume %q is without a storage capacity", volume.Name)
  539. }
  540. claimCap, ok := claim.Status.Capacity[api.ResourceStorage]
  541. if !ok || volumeCap.Cmp(claimCap) != 0 {
  542. claimClone.Status.Capacity = volume.Spec.Capacity
  543. dirty = true
  544. }
  545. }
  546. if !dirty {
  547. // Nothing to do.
  548. glog.V(4).Infof("updating PersistentVolumeClaim[%s] status: phase %s already set", claimToClaimKey(claim), phase)
  549. return claim, nil
  550. }
  551. newClaim, err := ctrl.kubeClient.Core().PersistentVolumeClaims(claimClone.Namespace).UpdateStatus(claimClone)
  552. if err != nil {
  553. glog.V(4).Infof("updating PersistentVolumeClaim[%s] status: set phase %s failed: %v", claimToClaimKey(claim), phase, err)
  554. return newClaim, err
  555. }
  556. _, err = ctrl.storeClaimUpdate(newClaim)
  557. if err != nil {
  558. glog.V(4).Infof("updating PersistentVolumeClaim[%s] status: cannot update internal cache: %v", claimToClaimKey(claim), err)
  559. return newClaim, err
  560. }
  561. glog.V(2).Infof("claim %q entered phase %q", claimToClaimKey(claim), phase)
  562. return newClaim, nil
  563. }
  564. // updateClaimStatusWithEvent saves new claim.Status to API server and emits
  565. // given event on the claim. It saves the status and emits the event only when
  566. // the status has actually changed from the version saved in API server.
  567. // Parameters:
  568. // claim - claim to update
  569. // phasephase - phase to set
  570. // volume - volume which Capacity is set into claim.Status.Capacity
  571. // eventtype, reason, message - event to send, see EventRecorder.Event()
  572. func (ctrl *PersistentVolumeController) updateClaimStatusWithEvent(claim *api.PersistentVolumeClaim, phase api.PersistentVolumeClaimPhase, volume *api.PersistentVolume, eventtype, reason, message string) (*api.PersistentVolumeClaim, error) {
  573. glog.V(4).Infof("updating updateClaimStatusWithEvent[%s]: set phase %s", claimToClaimKey(claim), phase)
  574. if claim.Status.Phase == phase {
  575. // Nothing to do.
  576. glog.V(4).Infof("updating updateClaimStatusWithEvent[%s]: phase %s already set", claimToClaimKey(claim), phase)
  577. return claim, nil
  578. }
  579. newClaim, err := ctrl.updateClaimStatus(claim, phase, volume)
  580. if err != nil {
  581. return nil, err
  582. }
  583. // Emit the event only when the status change happens, not every time
  584. // syncClaim is called.
  585. glog.V(3).Infof("claim %q changed status to %q: %s", claimToClaimKey(claim), phase, message)
  586. ctrl.eventRecorder.Event(newClaim, eventtype, reason, message)
  587. return newClaim, nil
  588. }
  589. // updateVolumePhase saves new volume phase to API server.
  590. func (ctrl *PersistentVolumeController) updateVolumePhase(volume *api.PersistentVolume, phase api.PersistentVolumePhase, message string) (*api.PersistentVolume, error) {
  591. glog.V(4).Infof("updating PersistentVolume[%s]: set phase %s", volume.Name, phase)
  592. if volume.Status.Phase == phase {
  593. // Nothing to do.
  594. glog.V(4).Infof("updating PersistentVolume[%s]: phase %s already set", volume.Name, phase)
  595. return volume, nil
  596. }
  597. clone, err := conversion.NewCloner().DeepCopy(volume)
  598. if err != nil {
  599. return nil, fmt.Errorf("Error cloning claim: %v", err)
  600. }
  601. volumeClone, ok := clone.(*api.PersistentVolume)
  602. if !ok {
  603. return nil, fmt.Errorf("Unexpected volume cast error : %v", volumeClone)
  604. }
  605. volumeClone.Status.Phase = phase
  606. volumeClone.Status.Message = message
  607. newVol, err := ctrl.kubeClient.Core().PersistentVolumes().UpdateStatus(volumeClone)
  608. if err != nil {
  609. glog.V(4).Infof("updating PersistentVolume[%s]: set phase %s failed: %v", volume.Name, phase, err)
  610. return newVol, err
  611. }
  612. _, err = ctrl.storeVolumeUpdate(newVol)
  613. if err != nil {
  614. glog.V(4).Infof("updating PersistentVolume[%s]: cannot update internal cache: %v", volume.Name, err)
  615. return newVol, err
  616. }
  617. glog.V(2).Infof("volume %q entered phase %q", volume.Name, phase)
  618. return newVol, err
  619. }
  620. // updateVolumePhaseWithEvent saves new volume phase to API server and emits
  621. // given event on the volume. It saves the phase and emits the event only when
  622. // the phase has actually changed from the version saved in API server.
  623. func (ctrl *PersistentVolumeController) updateVolumePhaseWithEvent(volume *api.PersistentVolume, phase api.PersistentVolumePhase, eventtype, reason, message string) (*api.PersistentVolume, error) {
  624. glog.V(4).Infof("updating updateVolumePhaseWithEvent[%s]: set phase %s", volume.Name, phase)
  625. if volume.Status.Phase == phase {
  626. // Nothing to do.
  627. glog.V(4).Infof("updating updateVolumePhaseWithEvent[%s]: phase %s already set", volume.Name, phase)
  628. return volume, nil
  629. }
  630. newVol, err := ctrl.updateVolumePhase(volume, phase, message)
  631. if err != nil {
  632. return nil, err
  633. }
  634. // Emit the event only when the status change happens, not every time
  635. // syncClaim is called.
  636. glog.V(3).Infof("volume %q changed status to %q: %s", volume.Name, phase, message)
  637. ctrl.eventRecorder.Event(newVol, eventtype, reason, message)
  638. return newVol, nil
  639. }
  640. // bindVolumeToClaim modifes given volume to be bound to a claim and saves it to
  641. // API server. The claim is not modified in this method!
  642. func (ctrl *PersistentVolumeController) bindVolumeToClaim(volume *api.PersistentVolume, claim *api.PersistentVolumeClaim) (*api.PersistentVolume, error) {
  643. glog.V(4).Infof("updating PersistentVolume[%s]: binding to %q", volume.Name, claimToClaimKey(claim))
  644. dirty := false
  645. // Check if the volume was already bound (either by user or by controller)
  646. shouldSetBoundByController := false
  647. if !isVolumeBoundToClaim(volume, claim) {
  648. shouldSetBoundByController = true
  649. }
  650. // The volume from method args can be pointing to watcher cache. We must not
  651. // modify these, therefore create a copy.
  652. clone, err := conversion.NewCloner().DeepCopy(volume)
  653. if err != nil {
  654. return nil, fmt.Errorf("Error cloning pv: %v", err)
  655. }
  656. volumeClone, ok := clone.(*api.PersistentVolume)
  657. if !ok {
  658. return nil, fmt.Errorf("Unexpected volume cast error : %v", volumeClone)
  659. }
  660. // Bind the volume to the claim if it is not bound yet
  661. if volume.Spec.ClaimRef == nil ||
  662. volume.Spec.ClaimRef.Name != claim.Name ||
  663. volume.Spec.ClaimRef.Namespace != claim.Namespace ||
  664. volume.Spec.ClaimRef.UID != claim.UID {
  665. claimRef, err := api.GetReference(claim)
  666. if err != nil {
  667. return nil, fmt.Errorf("Unexpected error getting claim reference: %v", err)
  668. }
  669. volumeClone.Spec.ClaimRef = claimRef
  670. dirty = true
  671. }
  672. // Set annBoundByController if it is not set yet
  673. if shouldSetBoundByController && !hasAnnotation(volumeClone.ObjectMeta, annBoundByController) {
  674. setAnnotation(&volumeClone.ObjectMeta, annBoundByController, "yes")
  675. dirty = true
  676. }
  677. // Save the volume only if something was changed
  678. if dirty {
  679. glog.V(2).Infof("claim %q bound to volume %q", claimToClaimKey(claim), volume.Name)
  680. newVol, err := ctrl.kubeClient.Core().PersistentVolumes().Update(volumeClone)
  681. if err != nil {
  682. glog.V(4).Infof("updating PersistentVolume[%s]: binding to %q failed: %v", volume.Name, claimToClaimKey(claim), err)
  683. return newVol, err
  684. }
  685. _, err = ctrl.storeVolumeUpdate(newVol)
  686. if err != nil {
  687. glog.V(4).Infof("updating PersistentVolume[%s]: cannot update internal cache: %v", volume.Name, err)
  688. return newVol, err
  689. }
  690. glog.V(4).Infof("updating PersistentVolume[%s]: bound to %q", newVol.Name, claimToClaimKey(claim))
  691. return newVol, nil
  692. }
  693. glog.V(4).Infof("updating PersistentVolume[%s]: already bound to %q", volume.Name, claimToClaimKey(claim))
  694. return volume, nil
  695. }
  696. // bindClaimToVolume modifies the given claim to be bound to a volume and
  697. // saves it to API server. The volume is not modified in this method!
  698. func (ctrl *PersistentVolumeController) bindClaimToVolume(claim *api.PersistentVolumeClaim, volume *api.PersistentVolume) (*api.PersistentVolumeClaim, error) {
  699. glog.V(4).Infof("updating PersistentVolumeClaim[%s]: binding to %q", claimToClaimKey(claim), volume.Name)
  700. dirty := false
  701. // Check if the claim was already bound (either by controller or by user)
  702. shouldSetBoundByController := false
  703. if volume.Name != claim.Spec.VolumeName {
  704. shouldSetBoundByController = true
  705. }
  706. // The claim from method args can be pointing to watcher cache. We must not
  707. // modify these, therefore create a copy.
  708. clone, err := conversion.NewCloner().DeepCopy(claim)
  709. if err != nil {
  710. return nil, fmt.Errorf("Error cloning claim: %v", err)
  711. }
  712. claimClone, ok := clone.(*api.PersistentVolumeClaim)
  713. if !ok {
  714. return nil, fmt.Errorf("Unexpected claim cast error : %v", claimClone)
  715. }
  716. // Bind the claim to the volume if it is not bound yet
  717. if claimClone.Spec.VolumeName != volume.Name {
  718. claimClone.Spec.VolumeName = volume.Name
  719. dirty = true
  720. }
  721. // Set annBoundByController if it is not set yet
  722. if shouldSetBoundByController && !hasAnnotation(claimClone.ObjectMeta, annBoundByController) {
  723. setAnnotation(&claimClone.ObjectMeta, annBoundByController, "yes")
  724. dirty = true
  725. }
  726. // Set annBindCompleted if it is not set yet
  727. if !hasAnnotation(claimClone.ObjectMeta, annBindCompleted) {
  728. setAnnotation(&claimClone.ObjectMeta, annBindCompleted, "yes")
  729. dirty = true
  730. }
  731. if dirty {
  732. glog.V(2).Infof("volume %q bound to claim %q", volume.Name, claimToClaimKey(claim))
  733. newClaim, err := ctrl.kubeClient.Core().PersistentVolumeClaims(claim.Namespace).Update(claimClone)
  734. if err != nil {
  735. glog.V(4).Infof("updating PersistentVolumeClaim[%s]: binding to %q failed: %v", claimToClaimKey(claim), volume.Name, err)
  736. return newClaim, err
  737. }
  738. _, err = ctrl.storeClaimUpdate(newClaim)
  739. if err != nil {
  740. glog.V(4).Infof("updating PersistentVolumeClaim[%s]: cannot update internal cache: %v", claimToClaimKey(claim), err)
  741. return newClaim, err
  742. }
  743. glog.V(4).Infof("updating PersistentVolumeClaim[%s]: bound to %q", claimToClaimKey(claim), volume.Name)
  744. return newClaim, nil
  745. }
  746. glog.V(4).Infof("updating PersistentVolumeClaim[%s]: already bound to %q", claimToClaimKey(claim), volume.Name)
  747. return claim, nil
  748. }
  749. // bind saves binding information both to the volume and the claim and marks
  750. // both objects as Bound. Volume is saved first.
  751. // It returns on first error, it's up to the caller to implement some retry
  752. // mechanism.
  753. func (ctrl *PersistentVolumeController) bind(volume *api.PersistentVolume, claim *api.PersistentVolumeClaim) error {
  754. var err error
  755. // use updateClaim/updatedVolume to keep the original claim/volume for
  756. // logging in error cases.
  757. var updatedClaim *api.PersistentVolumeClaim
  758. var updatedVolume *api.PersistentVolume
  759. glog.V(4).Infof("binding volume %q to claim %q", volume.Name, claimToClaimKey(claim))
  760. if updatedVolume, err = ctrl.bindVolumeToClaim(volume, claim); err != nil {
  761. glog.V(3).Infof("error binding volume %q to claim %q: failed saving the volume: %v", volume.Name, claimToClaimKey(claim), err)
  762. return err
  763. }
  764. volume = updatedVolume
  765. if updatedVolume, err = ctrl.updateVolumePhase(volume, api.VolumeBound, ""); err != nil {
  766. glog.V(3).Infof("error binding volume %q to claim %q: failed saving the volume status: %v", volume.Name, claimToClaimKey(claim), err)
  767. return err
  768. }
  769. volume = updatedVolume
  770. if updatedClaim, err = ctrl.bindClaimToVolume(claim, volume); err != nil {
  771. glog.V(3).Infof("error binding volume %q to claim %q: failed saving the claim: %v", volume.Name, claimToClaimKey(claim), err)
  772. return err
  773. }
  774. claim = updatedClaim
  775. if updatedClaim, err = ctrl.updateClaimStatus(claim, api.ClaimBound, volume); err != nil {
  776. glog.V(3).Infof("error binding volume %q to claim %q: failed saving the claim status: %v", volume.Name, claimToClaimKey(claim), err)
  777. return err
  778. }
  779. claim = updatedClaim
  780. glog.V(4).Infof("volume %q bound to claim %q", volume.Name, claimToClaimKey(claim))
  781. glog.V(4).Infof("volume %q status after binding: %s", volume.Name, getVolumeStatusForLogging(volume))
  782. glog.V(4).Infof("claim %q status after binding: %s", claimToClaimKey(claim), getClaimStatusForLogging(claim))
  783. return nil
  784. }
  785. // unbindVolume rolls back previous binding of the volume. This may be necessary
  786. // when two controllers bound two volumes to single claim - when we detect this,
  787. // only one binding succeeds and the second one must be rolled back.
  788. // This method updates both Spec and Status.
  789. // It returns on first error, it's up to the caller to implement some retry
  790. // mechanism.
  791. func (ctrl *PersistentVolumeController) unbindVolume(volume *api.PersistentVolume) error {
  792. glog.V(4).Infof("updating PersistentVolume[%s]: rolling back binding from %q", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
  793. // Save the PV only when any modification is neccessary.
  794. clone, err := conversion.NewCloner().DeepCopy(volume)
  795. if err != nil {
  796. return fmt.Errorf("Error cloning pv: %v", err)
  797. }
  798. volumeClone, ok := clone.(*api.PersistentVolume)
  799. if !ok {
  800. return fmt.Errorf("Unexpected volume cast error : %v", volumeClone)
  801. }
  802. if hasAnnotation(volume.ObjectMeta, annBoundByController) {
  803. // The volume was bound by the controller.
  804. volumeClone.Spec.ClaimRef = nil
  805. delete(volumeClone.Annotations, annBoundByController)
  806. if len(volumeClone.Annotations) == 0 {
  807. // No annotations look better than empty annotation map (and it's easier
  808. // to test).
  809. volumeClone.Annotations = nil
  810. }
  811. } else {
  812. // The volume was pre-bound by user. Clear only the binging UID.
  813. volumeClone.Spec.ClaimRef.UID = ""
  814. }
  815. newVol, err := ctrl.kubeClient.Core().PersistentVolumes().Update(volumeClone)
  816. if err != nil {
  817. glog.V(4).Infof("updating PersistentVolume[%s]: rollback failed: %v", volume.Name, err)
  818. return err
  819. }
  820. _, err = ctrl.storeVolumeUpdate(newVol)
  821. if err != nil {
  822. glog.V(4).Infof("updating PersistentVolume[%s]: cannot update internal cache: %v", volume.Name, err)
  823. return err
  824. }
  825. glog.V(4).Infof("updating PersistentVolume[%s]: rolled back", newVol.Name)
  826. // Update the status
  827. _, err = ctrl.updateVolumePhase(newVol, api.VolumeAvailable, "")
  828. return err
  829. }
  830. // reclaimVolume implements volume.Spec.PersistentVolumeReclaimPolicy and
  831. // starts appropriate reclaim action.
  832. func (ctrl *PersistentVolumeController) reclaimVolume(volume *api.PersistentVolume) error {
  833. switch volume.Spec.PersistentVolumeReclaimPolicy {
  834. case api.PersistentVolumeReclaimRetain:
  835. glog.V(4).Infof("reclaimVolume[%s]: policy is Retain, nothing to do", volume.Name)
  836. case api.PersistentVolumeReclaimRecycle:
  837. glog.V(4).Infof("reclaimVolume[%s]: policy is Recycle", volume.Name)
  838. opName := fmt.Sprintf("recycle-%s[%s]", volume.Name, string(volume.UID))
  839. ctrl.scheduleOperation(opName, func() error {
  840. ctrl.recycleVolumeOperation(volume)
  841. return nil
  842. })
  843. case api.PersistentVolumeReclaimDelete:
  844. glog.V(4).Infof("reclaimVolume[%s]: policy is Delete", volume.Name)
  845. opName := fmt.Sprintf("delete-%s[%s]", volume.Name, string(volume.UID))
  846. ctrl.scheduleOperation(opName, func() error {
  847. ctrl.deleteVolumeOperation(volume)
  848. return nil
  849. })
  850. default:
  851. // Unknown PersistentVolumeReclaimPolicy
  852. if _, err := ctrl.updateVolumePhaseWithEvent(volume, api.VolumeFailed, api.EventTypeWarning, "VolumeUnknownReclaimPolicy", "Volume has unrecognized PersistentVolumeReclaimPolicy"); err != nil {
  853. return err
  854. }
  855. }
  856. return nil
  857. }
  858. // doRerecycleVolumeOperationcycleVolume recycles a volume. This method is
  859. // running in standalone goroutine and already has all necessary locks.
  860. func (ctrl *PersistentVolumeController) recycleVolumeOperation(arg interface{}) {
  861. volume, ok := arg.(*api.PersistentVolume)
  862. if !ok {
  863. glog.Errorf("Cannot convert recycleVolumeOperation argument to volume, got %#v", arg)
  864. return
  865. }
  866. glog.V(4).Infof("recycleVolumeOperation [%s] started", volume.Name)
  867. // This method may have been waiting for a volume lock for some time.
  868. // Previous recycleVolumeOperation might just have saved an updated version,
  869. // so read current volume state now.
  870. newVolume, err := ctrl.kubeClient.Core().PersistentVolumes().Get(volume.Name)
  871. if err != nil {
  872. glog.V(3).Infof("error reading peristent volume %q: %v", volume.Name, err)
  873. return
  874. }
  875. needsReclaim, err := ctrl.isVolumeReleased(newVolume)
  876. if err != nil {
  877. glog.V(3).Infof("error reading claim for volume %q: %v", volume.Name, err)
  878. return
  879. }
  880. if !needsReclaim {
  881. glog.V(3).Infof("volume %q no longer needs recycling, skipping", volume.Name)
  882. return
  883. }
  884. // Use the newest volume copy, this will save us from version conflicts on
  885. // saving.
  886. volume = newVolume
  887. // Find a plugin.
  888. spec := vol.NewSpecFromPersistentVolume(volume, false)
  889. plugin, err := ctrl.volumePluginMgr.FindRecyclablePluginBySpec(spec)
  890. if err != nil {
  891. // No recycler found. Emit an event and mark the volume Failed.
  892. if _, err = ctrl.updateVolumePhaseWithEvent(volume, api.VolumeFailed, api.EventTypeWarning, "VolumeFailedRecycle", "No recycler plugin found for the volume!"); err != nil {
  893. glog.V(4).Infof("recycleVolumeOperation [%s]: failed to mark volume as failed: %v", volume.Name, err)
  894. // Save failed, retry on the next deletion attempt
  895. return
  896. }
  897. // Despite the volume being Failed, the controller will retry recycling
  898. // the volume in every syncVolume() call.
  899. return
  900. }
  901. // Plugin found
  902. recycler, err := plugin.NewRecycler(volume.Name, spec)
  903. if err != nil {
  904. // Cannot create recycler
  905. strerr := fmt.Sprintf("Failed to create recycler: %v", err)
  906. if _, err = ctrl.updateVolumePhaseWithEvent(volume, api.VolumeFailed, api.EventTypeWarning, "VolumeFailedRecycle", strerr); err != nil {
  907. glog.V(4).Infof("recycleVolumeOperation [%s]: failed to mark volume as failed: %v", volume.Name, err)
  908. // Save failed, retry on the next deletion attempt
  909. return
  910. }
  911. // Despite the volume being Failed, the controller will retry recycling
  912. // the volume in every syncVolume() call.
  913. return
  914. }
  915. if err = recycler.Recycle(); err != nil {
  916. // Recycler failed
  917. strerr := fmt.Sprintf("Recycler failed: %s", err)
  918. if _, err = ctrl.updateVolumePhaseWithEvent(volume, api.VolumeFailed, api.EventTypeWarning, "VolumeFailedRecycle", strerr); err != nil {
  919. glog.V(4).Infof("recycleVolumeOperation [%s]: failed to mark volume as failed: %v", volume.Name, err)
  920. // Save failed, retry on the next deletion attempt
  921. return
  922. }
  923. // Despite the volume being Failed, the controller will retry recycling
  924. // the volume in every syncVolume() call.
  925. return
  926. }
  927. glog.V(2).Infof("volume %q recycled", volume.Name)
  928. // Make the volume available again
  929. if err = ctrl.unbindVolume(volume); err != nil {
  930. // Oops, could not save the volume and therefore the controller will
  931. // recycle the volume again on next update. We _could_ maintain a cache
  932. // of "recently recycled volumes" and avoid unnecessary recycling, this
  933. // is left out as future optimization.
  934. glog.V(3).Infof("recycleVolumeOperation [%s]: failed to make recycled volume 'Available' (%v), we will recycle the volume again", volume.Name, err)
  935. return
  936. }
  937. return
  938. }
  939. // deleteVolumeOperation deletes a volume. This method is running in standalone
  940. // goroutine and already has all necessary locks.
  941. func (ctrl *PersistentVolumeController) deleteVolumeOperation(arg interface{}) {
  942. volume, ok := arg.(*api.PersistentVolume)
  943. if !ok {
  944. glog.Errorf("Cannot convert deleteVolumeOperation argument to volume, got %#v", arg)
  945. return
  946. }
  947. glog.V(4).Infof("deleteVolumeOperation [%s] started", volume.Name)
  948. // This method may have been waiting for a volume lock for some time.
  949. // Previous deleteVolumeOperation might just have saved an updated version, so
  950. // read current volume state now.
  951. newVolume, err := ctrl.kubeClient.Core().PersistentVolumes().Get(volume.Name)
  952. if err != nil {
  953. glog.V(3).Infof("error reading peristent volume %q: %v", volume.Name, err)
  954. return
  955. }
  956. needsReclaim, err := ctrl.isVolumeReleased(newVolume)
  957. if err != nil {
  958. glog.V(3).Infof("error reading claim for volume %q: %v", volume.Name, err)
  959. return
  960. }
  961. if !needsReclaim {
  962. glog.V(3).Infof("volume %q no longer needs deletion, skipping", volume.Name)
  963. return
  964. }
  965. if err = ctrl.doDeleteVolume(volume); err != nil {
  966. // Delete failed, update the volume and emit an event.
  967. glog.V(3).Infof("deletion of volume %q failed: %v", volume.Name, err)
  968. if _, err = ctrl.updateVolumePhaseWithEvent(volume, api.VolumeFailed, api.EventTypeWarning, "VolumeFailedDelete", err.Error()); err != nil {
  969. glog.V(4).Infof("deleteVolumeOperation [%s]: failed to mark volume as failed: %v", volume.Name, err)
  970. // Save failed, retry on the next deletion attempt
  971. return
  972. }
  973. // Despite the volume being Failed, the controller will retry deleting
  974. // the volume in every syncVolume() call.
  975. return
  976. }
  977. glog.V(4).Infof("deleteVolumeOperation [%s]: success", volume.Name)
  978. // Delete the volume
  979. if err = ctrl.kubeClient.Core().PersistentVolumes().Delete(volume.Name, nil); err != nil {
  980. // Oops, could not delete the volume and therefore the controller will
  981. // try to delete the volume again on next update. We _could_ maintain a
  982. // cache of "recently deleted volumes" and avoid unnecessary deletion,
  983. // this is left out as future optimization.
  984. glog.V(3).Infof("failed to delete volume %q from database: %v", volume.Name, err)
  985. return
  986. }
  987. return
  988. }
  989. // isVolumeReleased returns true if given volume is released and can be recycled
  990. // or deleted, based on its retain policy. I.e. the volume is bound to a claim
  991. // and the claim does not exist or exists and is bound to different volume.
  992. func (ctrl *PersistentVolumeController) isVolumeReleased(volume *api.PersistentVolume) (bool, error) {
  993. // A volume needs reclaim if it has ClaimRef and appropriate claim does not
  994. // exist.
  995. if volume.Spec.ClaimRef == nil {
  996. glog.V(4).Infof("isVolumeReleased[%s]: ClaimRef is nil", volume.Name)
  997. return false, nil
  998. }
  999. if volume.Spec.ClaimRef.UID == "" {
  1000. // This is a volume bound by user and the controller has not finished
  1001. // binding to the real claim yet.
  1002. glog.V(4).Infof("isVolumeReleased[%s]: ClaimRef is not bound", volume.Name)
  1003. return false, nil
  1004. }
  1005. var claim *api.PersistentVolumeClaim
  1006. claimName := claimrefToClaimKey(volume.Spec.ClaimRef)
  1007. obj, found, err := ctrl.claims.GetByKey(claimName)
  1008. if err != nil {
  1009. return false, err
  1010. }
  1011. if !found {
  1012. // Fall through with claim = nil
  1013. } else {
  1014. var ok bool
  1015. claim, ok = obj.(*api.PersistentVolumeClaim)
  1016. if !ok {
  1017. return false, fmt.Errorf("Cannot convert object from claim cache to claim!?: %#v", obj)
  1018. }
  1019. }
  1020. if claim != nil && claim.UID == volume.Spec.ClaimRef.UID {
  1021. // the claim still exists and has the right UID
  1022. glog.V(4).Infof("isVolumeReleased[%s]: ClaimRef is still valid, volume is not released", volume.Name)
  1023. return false, nil
  1024. }
  1025. glog.V(2).Infof("isVolumeReleased[%s]: volume is released", volume.Name)
  1026. return true, nil
  1027. }
  1028. // doDeleteVolume finds appropriate delete plugin and deletes given volume
  1029. // (it will be re-used in future provisioner error cases).
  1030. func (ctrl *PersistentVolumeController) doDeleteVolume(volume *api.PersistentVolume) error {
  1031. glog.V(4).Infof("doDeleteVolume [%s]", volume.Name)
  1032. var err error
  1033. // Find a plugin. Try to find the same plugin that provisioned the volume
  1034. var plugin vol.DeletableVolumePlugin
  1035. if hasAnnotation(volume.ObjectMeta, annDynamicallyProvisioned) {
  1036. provisionPluginName := volume.Annotations[annDynamicallyProvisioned]
  1037. if provisionPluginName != "" {
  1038. plugin, err = ctrl.volumePluginMgr.FindDeletablePluginByName(provisionPluginName)
  1039. if err != nil {
  1040. glog.V(3).Infof("did not find a deleter plugin %q for volume %q: %v, will try to find a generic one",
  1041. provisionPluginName, volume.Name, err)
  1042. }
  1043. }
  1044. }
  1045. spec := vol.NewSpecFromPersistentVolume(volume, false)
  1046. if plugin == nil {
  1047. // The plugin that provisioned the volume was not found or the volume
  1048. // was not dynamically provisioned. Try to find a plugin by spec.
  1049. plugin, err = ctrl.volumePluginMgr.FindDeletablePluginBySpec(spec)
  1050. if err != nil {
  1051. // No deleter found. Emit an event and mark the volume Failed.
  1052. return fmt.Errorf("Error getting deleter volume plugin for volume %q: %v", volume.Name, err)
  1053. }
  1054. }
  1055. glog.V(5).Infof("found a deleter plugin %q for volume %q", plugin.GetPluginName(), volume.Name)
  1056. // Plugin found
  1057. deleter, err := plugin.NewDeleter(spec)
  1058. if err != nil {
  1059. // Cannot create deleter
  1060. return fmt.Errorf("Failed to create deleter for volume %q: %v", volume.Name, err)
  1061. }
  1062. if err = deleter.Delete(); err != nil {
  1063. // Deleter failed
  1064. return fmt.Errorf("Delete of volume %q failed: %v", volume.Name, err)
  1065. }
  1066. glog.V(2).Infof("volume %q deleted", volume.Name)
  1067. return nil
  1068. }
  1069. // provisionClaim starts new asynchronous operation to provision a claim if
  1070. // provisioning is enabled.
  1071. func (ctrl *PersistentVolumeController) provisionClaim(claim *api.PersistentVolumeClaim) error {
  1072. if !ctrl.enableDynamicProvisioning {
  1073. return nil
  1074. }
  1075. glog.V(4).Infof("provisionClaim[%s]: started", claimToClaimKey(claim))
  1076. opName := fmt.Sprintf("provision-%s[%s]", claimToClaimKey(claim), string(claim.UID))
  1077. ctrl.scheduleOperation(opName, func() error {
  1078. ctrl.provisionClaimOperation(claim)
  1079. return nil
  1080. })
  1081. return nil
  1082. }
  1083. // provisionClaimOperation provisions a volume. This method is running in
  1084. // standalone goroutine and already has all necessary locks.
  1085. func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interface{}) {
  1086. claim, ok := claimObj.(*api.PersistentVolumeClaim)
  1087. if !ok {
  1088. glog.Errorf("Cannot convert provisionClaimOperation argument to claim, got %#v", claimObj)
  1089. return
  1090. }
  1091. claimClass := getClaimClass(claim)
  1092. glog.V(4).Infof("provisionClaimOperation [%s] started, class: %q", claimToClaimKey(claim), claimClass)
  1093. // A previous doProvisionClaim may just have finished while we were waiting for
  1094. // the locks. Check that PV (with deterministic name) hasn't been provisioned
  1095. // yet.
  1096. pvName := ctrl.getProvisionedVolumeNameForClaim(claim)
  1097. volume, err := ctrl.kubeClient.Core().PersistentVolumes().Get(pvName)
  1098. if err == nil && volume != nil {
  1099. // Volume has been already provisioned, nothing to do.
  1100. glog.V(4).Infof("provisionClaimOperation [%s]: volume already exists, skipping", claimToClaimKey(claim))
  1101. return
  1102. }
  1103. // Prepare a claimRef to the claim early (to fail before a volume is
  1104. // provisioned)
  1105. claimRef, err := api.GetReference(claim)
  1106. if err != nil {
  1107. glog.V(3).Infof("unexpected error getting claim reference: %v", err)
  1108. return
  1109. }
  1110. plugin, storageClass, err := ctrl.findProvisionablePlugin(claim)
  1111. if err != nil {
  1112. ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", err.Error())
  1113. glog.V(2).Infof("error finding provisioning plugin for claim %s: %v", claimToClaimKey(claim), err)
  1114. // The controller will retry provisioning the volume in every
  1115. // syncVolume() call.
  1116. return
  1117. }
  1118. // Gather provisioning options
  1119. tags := make(map[string]string)
  1120. tags[cloudVolumeCreatedForClaimNamespaceTag] = claim.Namespace
  1121. tags[cloudVolumeCreatedForClaimNameTag] = claim.Name
  1122. tags[cloudVolumeCreatedForVolumeNameTag] = pvName
  1123. options := vol.VolumeOptions{
  1124. Capacity: claim.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)],
  1125. AccessModes: claim.Spec.AccessModes,
  1126. PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
  1127. CloudTags: &tags,
  1128. ClusterName: ctrl.clusterName,
  1129. PVName: pvName,
  1130. PVCName: claim.Name,
  1131. Parameters: storageClass.Parameters,
  1132. Selector: claim.Spec.Selector,
  1133. }
  1134. // Provision the volume
  1135. provisioner, err := plugin.NewProvisioner(options)
  1136. if err != nil {
  1137. strerr := fmt.Sprintf("Failed to create provisioner: %v", err)
  1138. glog.V(2).Infof("failed to create provisioner for claim %q with StorageClass %q: %v", claimToClaimKey(claim), storageClass.Name, err)
  1139. ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", strerr)
  1140. return
  1141. }
  1142. volume, err = provisioner.Provision()
  1143. if err != nil {
  1144. strerr := fmt.Sprintf("Failed to provision volume with StorageClass %q: %v", storageClass.Name, err)
  1145. glog.V(2).Infof("failed to provision volume for claim %q with StorageClass %q: %v", claimToClaimKey(claim), storageClass.Name, err)
  1146. ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", strerr)
  1147. return
  1148. }
  1149. glog.V(3).Infof("volume %q for claim %q created", volume.Name, claimToClaimKey(claim))
  1150. // Create Kubernetes PV object for the volume.
  1151. volume.Name = pvName
  1152. // Bind it to the claim
  1153. volume.Spec.ClaimRef = claimRef
  1154. volume.Status.Phase = api.VolumeBound
  1155. // Add annBoundByController (used in deleting the volume)
  1156. setAnnotation(&volume.ObjectMeta, annBoundByController, "yes")
  1157. setAnnotation(&volume.ObjectMeta, annDynamicallyProvisioned, plugin.GetPluginName())
  1158. // For Alpha provisioning behavior, do not add annClass for volumes created
  1159. // by annAlphaClass
  1160. // TODO: remove this check in 1.5, annClass will be always non-empty there.
  1161. if claimClass != "" {
  1162. setAnnotation(&volume.ObjectMeta, annClass, claimClass)
  1163. }
  1164. // Try to create the PV object several times
  1165. for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ {
  1166. glog.V(4).Infof("provisionClaimOperation [%s]: trying to save volume %s", claimToClaimKey(claim), volume.Name)
  1167. var newVol *api.PersistentVolume
  1168. if newVol, err = ctrl.kubeClient.Core().PersistentVolumes().Create(volume); err == nil {
  1169. // Save succeeded.
  1170. glog.V(3).Infof("volume %q for claim %q saved", volume.Name, claimToClaimKey(claim))
  1171. _, err = ctrl.storeVolumeUpdate(newVol)
  1172. if err != nil {
  1173. // We will get an "volume added" event soon, this is not a big error
  1174. glog.V(4).Infof("provisionClaimOperation [%s]: cannot update internal cache: %v", volume.Name, err)
  1175. }
  1176. break
  1177. }
  1178. // Save failed, try again after a while.
  1179. glog.V(3).Infof("failed to save volume %q for claim %q: %v", volume.Name, claimToClaimKey(claim), err)
  1180. time.Sleep(ctrl.createProvisionedPVInterval)
  1181. }
  1182. if err != nil {
  1183. // Save failed. Now we have a storage asset outside of Kubernetes,
  1184. // but we don't have appropriate PV object for it.
  1185. // Emit some event here and try to delete the storage asset several
  1186. // times.
  1187. strerr := fmt.Sprintf("Error creating provisioned PV object for claim %s: %v. Deleting the volume.", claimToClaimKey(claim), err)
  1188. glog.V(3).Info(strerr)
  1189. ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", strerr)
  1190. for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ {
  1191. if err = ctrl.doDeleteVolume(volume); err == nil {
  1192. // Delete succeeded
  1193. glog.V(4).Infof("provisionClaimOperation [%s]: cleaning volume %s succeeded", claimToClaimKey(claim), volume.Name)
  1194. break
  1195. }
  1196. // Delete failed, try again after a while.
  1197. glog.V(3).Infof("failed to delete volume %q: %v", volume.Name, err)
  1198. time.Sleep(ctrl.createProvisionedPVInterval)
  1199. }
  1200. if err != nil {
  1201. // Delete failed several times. There is an orphaned volume and there
  1202. // is nothing we can do about it.
  1203. strerr := fmt.Sprintf("Error cleaning provisioned volume for claim %s: %v. Please delete manually.", claimToClaimKey(claim), err)
  1204. glog.V(2).Info(strerr)
  1205. ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningCleanupFailed", strerr)
  1206. }
  1207. } else {
  1208. glog.V(2).Infof("volume %q provisioned for claim %q", volume.Name, claimToClaimKey(claim))
  1209. }
  1210. }
  1211. // getProvisionedVolumeNameForClaim returns PV.Name for the provisioned volume.
  1212. // The name must be unique.
  1213. func (ctrl *PersistentVolumeController) getProvisionedVolumeNameForClaim(claim *api.PersistentVolumeClaim) string {
  1214. return "pvc-" + string(claim.UID)
  1215. }
  1216. // scheduleOperation starts given asynchronous operation on given volume. It
  1217. // makes sure the operation is already not running.
  1218. func (ctrl *PersistentVolumeController) scheduleOperation(operationName string, operation func() error) {
  1219. glog.V(4).Infof("scheduleOperation[%s]", operationName)
  1220. // Poke test code that an operation is just about to get started.
  1221. if ctrl.preOperationHook != nil {
  1222. ctrl.preOperationHook(operationName)
  1223. }
  1224. err := ctrl.runningOperations.Run(operationName, operation)
  1225. if err != nil {
  1226. if goroutinemap.IsAlreadyExists(err) {
  1227. glog.V(4).Infof("operation %q is already running, skipping", operationName)
  1228. } else {
  1229. glog.Errorf("error scheduling operaion %q: %v", operationName, err)
  1230. }
  1231. }
  1232. }
  1233. func (ctrl *PersistentVolumeController) findProvisionablePlugin(claim *api.PersistentVolumeClaim) (vol.ProvisionableVolumePlugin, *extensions.StorageClass, error) {
  1234. // TODO: remove this alpha behavior in 1.5
  1235. alpha := hasAnnotation(claim.ObjectMeta, annAlphaClass)
  1236. beta := hasAnnotation(claim.ObjectMeta, annClass)
  1237. if alpha && beta {
  1238. // Both Alpha and Beta annotations are set. Do beta.
  1239. alpha = false
  1240. msg := fmt.Sprintf("both %q and %q annotations are present, using %q", annAlphaClass, annClass, annClass)
  1241. ctrl.eventRecorder.Event(claim, api.EventTypeNormal, "ProvisioningIgnoreAlpha", msg)
  1242. }
  1243. if alpha {
  1244. // Fall back to fixed list of provisioner plugins
  1245. return ctrl.findAlphaProvisionablePlugin()
  1246. }
  1247. // provisionClaim() which leads here is never called with claimClass=="", we
  1248. // can save some checks.
  1249. claimClass := getClaimClass(claim)
  1250. classObj, found, err := ctrl.classes.GetByKey(claimClass)
  1251. if err != nil {
  1252. return nil, nil, err
  1253. }
  1254. if !found {
  1255. return nil, nil, fmt.Errorf("StorageClass %q not found", claimClass)
  1256. }
  1257. class, ok := classObj.(*extensions.StorageClass)
  1258. if !ok {
  1259. return nil, nil, fmt.Errorf("Cannot convert object to StorageClass: %+v", classObj)
  1260. }
  1261. // Find a plugin for the class
  1262. plugin, err := ctrl.volumePluginMgr.FindProvisionablePluginByName(class.Provisioner)
  1263. if err != nil {
  1264. return nil, nil, err
  1265. }
  1266. return plugin, class, nil
  1267. }
  1268. // findAlphaProvisionablePlugin returns a volume plugin compatible with
  1269. // Kubernetes 1.3.
  1270. // TODO: remove in Kubernetes 1.5
  1271. func (ctrl *PersistentVolumeController) findAlphaProvisionablePlugin() (vol.ProvisionableVolumePlugin, *extensions.StorageClass, error) {
  1272. if ctrl.alphaProvisioner == nil {
  1273. return nil, nil, fmt.Errorf("cannot find volume plugin for alpha provisioning")
  1274. }
  1275. // Return a dummy StorageClass instance with no parameters
  1276. storageClass := &extensions.StorageClass{
  1277. TypeMeta: unversioned.TypeMeta{
  1278. Kind: "StorageClass",
  1279. },
  1280. ObjectMeta: api.ObjectMeta{
  1281. Name: "",
  1282. },
  1283. Provisioner: ctrl.alphaProvisioner.GetPluginName(),
  1284. }
  1285. glog.V(4).Infof("using alpha provisioner %s", ctrl.alphaProvisioner.GetPluginName())
  1286. return ctrl.alphaProvisioner, storageClass, nil
  1287. }