testing.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  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 testing
  14. import (
  15. "errors"
  16. "fmt"
  17. "reflect"
  18. "strconv"
  19. "sync"
  20. v1 "k8s.io/api/core/v1"
  21. apierrs "k8s.io/apimachinery/pkg/api/errors"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/runtime"
  24. "k8s.io/apimachinery/pkg/runtime/schema"
  25. "k8s.io/apimachinery/pkg/util/diff"
  26. "k8s.io/apimachinery/pkg/watch"
  27. utilfeature "k8s.io/apiserver/pkg/util/feature"
  28. "k8s.io/client-go/kubernetes/fake"
  29. core "k8s.io/client-go/testing"
  30. "k8s.io/klog"
  31. "k8s.io/kubernetes/pkg/features"
  32. )
  33. // ErrVersionConflict is the error returned when resource version of requested
  34. // object conflicts with the object in storage.
  35. var ErrVersionConflict = errors.New("VersionError")
  36. // VolumeReactor is a core.Reactor that simulates etcd and API server. It
  37. // stores:
  38. // - Latest version of claims volumes saved by the controller.
  39. // - Queue of all saves (to simulate "volume/claim updated" events). This queue
  40. // contains all intermediate state of an object - e.g. a claim.VolumeName
  41. // is updated first and claim.Phase second. This queue will then contain both
  42. // updates as separate entries.
  43. // - Number of changes since the last call to VolumeReactor.syncAll().
  44. // - Optionally, volume and claim fake watchers which should be the same ones
  45. // used by the controller. Any time an event function like deleteVolumeEvent
  46. // is called to simulate an event, the reactor's stores are updated and the
  47. // controller is sent the event via the fake watcher.
  48. // - Optionally, list of error that should be returned by reactor, simulating
  49. // etcd / API server failures. These errors are evaluated in order and every
  50. // error is returned only once. I.e. when the reactor finds matching
  51. // ReactorError, it return appropriate error and removes the ReactorError from
  52. // the list.
  53. type VolumeReactor struct {
  54. volumes map[string]*v1.PersistentVolume
  55. claims map[string]*v1.PersistentVolumeClaim
  56. changedObjects []interface{}
  57. changedSinceLastSync int
  58. fakeVolumeWatch *watch.FakeWatcher
  59. fakeClaimWatch *watch.FakeWatcher
  60. lock sync.RWMutex
  61. errors []ReactorError
  62. watchers map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher
  63. }
  64. // ReactorError is an error that is returned by test reactor (=simulated
  65. // etcd+/API server) when an action performed by the reactor matches given verb
  66. // ("get", "update", "create", "delete" or "*"") on given resource
  67. // ("persistentvolumes", "persistentvolumeclaims" or "*").
  68. type ReactorError struct {
  69. Verb string
  70. Resource string
  71. Error error
  72. }
  73. // React is a callback called by fake kubeClient from the controller.
  74. // In other words, every claim/volume change performed by the controller ends
  75. // here.
  76. // This callback checks versions of the updated objects and refuse those that
  77. // are too old (simulating real etcd).
  78. // All updated objects are stored locally to keep track of object versions and
  79. // to evaluate test results.
  80. // All updated objects are also inserted into changedObjects queue and
  81. // optionally sent back to the controller via its watchers.
  82. func (r *VolumeReactor) React(action core.Action) (handled bool, ret runtime.Object, err error) {
  83. r.lock.Lock()
  84. defer r.lock.Unlock()
  85. klog.V(4).Infof("reactor got operation %q on %q", action.GetVerb(), action.GetResource())
  86. // Inject error when requested
  87. err = r.injectReactError(action)
  88. if err != nil {
  89. return true, nil, err
  90. }
  91. // Test did not request to inject an error, continue simulating API server.
  92. switch {
  93. case action.Matches("create", "persistentvolumes"):
  94. obj := action.(core.UpdateAction).GetObject()
  95. volume := obj.(*v1.PersistentVolume)
  96. // check the volume does not exist
  97. _, found := r.volumes[volume.Name]
  98. if found {
  99. return true, nil, fmt.Errorf("Cannot create volume %s: volume already exists", volume.Name)
  100. }
  101. // mimic apiserver defaulting
  102. if volume.Spec.VolumeMode == nil && utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
  103. volume.Spec.VolumeMode = new(v1.PersistentVolumeMode)
  104. *volume.Spec.VolumeMode = v1.PersistentVolumeFilesystem
  105. }
  106. // Store the updated object to appropriate places.
  107. r.volumes[volume.Name] = volume
  108. for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
  109. w.Add(volume)
  110. }
  111. r.changedObjects = append(r.changedObjects, volume)
  112. r.changedSinceLastSync++
  113. klog.V(4).Infof("created volume %s", volume.Name)
  114. return true, volume, nil
  115. case action.Matches("create", "persistentvolumeclaims"):
  116. obj := action.(core.UpdateAction).GetObject()
  117. claim := obj.(*v1.PersistentVolumeClaim)
  118. // check the claim does not exist
  119. _, found := r.claims[claim.Name]
  120. if found {
  121. return true, nil, fmt.Errorf("Cannot create claim %s: claim already exists", claim.Name)
  122. }
  123. // Store the updated object to appropriate places.
  124. r.claims[claim.Name] = claim
  125. for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
  126. w.Add(claim)
  127. }
  128. r.changedObjects = append(r.changedObjects, claim)
  129. r.changedSinceLastSync++
  130. klog.V(4).Infof("created claim %s", claim.Name)
  131. return true, claim, nil
  132. case action.Matches("update", "persistentvolumes"):
  133. obj := action.(core.UpdateAction).GetObject()
  134. volume := obj.(*v1.PersistentVolume)
  135. // Check and bump object version
  136. storedVolume, found := r.volumes[volume.Name]
  137. if found {
  138. storedVer, _ := strconv.Atoi(storedVolume.ResourceVersion)
  139. requestedVer, _ := strconv.Atoi(volume.ResourceVersion)
  140. if storedVer != requestedVer {
  141. return true, obj, ErrVersionConflict
  142. }
  143. if reflect.DeepEqual(storedVolume, volume) {
  144. klog.V(4).Infof("nothing updated volume %s", volume.Name)
  145. return true, volume, nil
  146. }
  147. // Don't modify the existing object
  148. volume = volume.DeepCopy()
  149. volume.ResourceVersion = strconv.Itoa(storedVer + 1)
  150. } else {
  151. return true, nil, fmt.Errorf("Cannot update volume %s: volume not found", volume.Name)
  152. }
  153. // Store the updated object to appropriate places.
  154. for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
  155. w.Modify(volume)
  156. }
  157. r.volumes[volume.Name] = volume
  158. r.changedObjects = append(r.changedObjects, volume)
  159. r.changedSinceLastSync++
  160. klog.V(4).Infof("saved updated volume %s", volume.Name)
  161. return true, volume, nil
  162. case action.Matches("update", "persistentvolumeclaims"):
  163. obj := action.(core.UpdateAction).GetObject()
  164. claim := obj.(*v1.PersistentVolumeClaim)
  165. // Check and bump object version
  166. storedClaim, found := r.claims[claim.Name]
  167. if found {
  168. storedVer, _ := strconv.Atoi(storedClaim.ResourceVersion)
  169. requestedVer, _ := strconv.Atoi(claim.ResourceVersion)
  170. if storedVer != requestedVer {
  171. return true, obj, ErrVersionConflict
  172. }
  173. if reflect.DeepEqual(storedClaim, claim) {
  174. klog.V(4).Infof("nothing updated claim %s", claim.Name)
  175. return true, claim, nil
  176. }
  177. // Don't modify the existing object
  178. claim = claim.DeepCopy()
  179. claim.ResourceVersion = strconv.Itoa(storedVer + 1)
  180. } else {
  181. return true, nil, fmt.Errorf("Cannot update claim %s: claim not found", claim.Name)
  182. }
  183. // Store the updated object to appropriate places.
  184. for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
  185. w.Modify(claim)
  186. }
  187. r.claims[claim.Name] = claim
  188. r.changedObjects = append(r.changedObjects, claim)
  189. r.changedSinceLastSync++
  190. klog.V(4).Infof("saved updated claim %s", claim.Name)
  191. return true, claim, nil
  192. case action.Matches("get", "persistentvolumes"):
  193. name := action.(core.GetAction).GetName()
  194. volume, found := r.volumes[name]
  195. if found {
  196. klog.V(4).Infof("GetVolume: found %s", volume.Name)
  197. return true, volume.DeepCopy(), nil
  198. }
  199. klog.V(4).Infof("GetVolume: volume %s not found", name)
  200. return true, nil, fmt.Errorf("Cannot find volume %s", name)
  201. case action.Matches("get", "persistentvolumeclaims"):
  202. name := action.(core.GetAction).GetName()
  203. claim, found := r.claims[name]
  204. if found {
  205. klog.V(4).Infof("GetClaim: found %s", claim.Name)
  206. return true, claim.DeepCopy(), nil
  207. }
  208. klog.V(4).Infof("GetClaim: claim %s not found", name)
  209. return true, nil, apierrs.NewNotFound(action.GetResource().GroupResource(), name)
  210. case action.Matches("delete", "persistentvolumes"):
  211. name := action.(core.DeleteAction).GetName()
  212. klog.V(4).Infof("deleted volume %s", name)
  213. obj, found := r.volumes[name]
  214. if found {
  215. delete(r.volumes, name)
  216. for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
  217. w.Delete(obj)
  218. }
  219. r.changedSinceLastSync++
  220. return true, nil, nil
  221. }
  222. return true, nil, fmt.Errorf("Cannot delete volume %s: not found", name)
  223. case action.Matches("delete", "persistentvolumeclaims"):
  224. name := action.(core.DeleteAction).GetName()
  225. klog.V(4).Infof("deleted claim %s", name)
  226. obj, found := r.claims[name]
  227. if found {
  228. delete(r.claims, name)
  229. for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
  230. w.Delete(obj)
  231. }
  232. r.changedSinceLastSync++
  233. return true, nil, nil
  234. }
  235. return true, nil, fmt.Errorf("Cannot delete claim %s: not found", name)
  236. }
  237. return false, nil, nil
  238. }
  239. // Watch watches objects from the VolumeReactor. Watch returns a channel which
  240. // will push added / modified / deleted object.
  241. func (r *VolumeReactor) Watch(gvr schema.GroupVersionResource, ns string) (watch.Interface, error) {
  242. r.lock.Lock()
  243. defer r.lock.Unlock()
  244. fakewatcher := watch.NewRaceFreeFake()
  245. if _, exists := r.watchers[gvr]; !exists {
  246. r.watchers[gvr] = make(map[string][]*watch.RaceFreeFakeWatcher)
  247. }
  248. r.watchers[gvr][ns] = append(r.watchers[gvr][ns], fakewatcher)
  249. return fakewatcher, nil
  250. }
  251. func (r *VolumeReactor) getWatches(gvr schema.GroupVersionResource, ns string) []*watch.RaceFreeFakeWatcher {
  252. watches := []*watch.RaceFreeFakeWatcher{}
  253. if r.watchers[gvr] != nil {
  254. if w := r.watchers[gvr][ns]; w != nil {
  255. watches = append(watches, w...)
  256. }
  257. if ns != metav1.NamespaceAll {
  258. if w := r.watchers[gvr][metav1.NamespaceAll]; w != nil {
  259. watches = append(watches, w...)
  260. }
  261. }
  262. }
  263. return watches
  264. }
  265. // injectReactError returns an error when the test requested given action to
  266. // fail. nil is returned otherwise.
  267. func (r *VolumeReactor) injectReactError(action core.Action) error {
  268. if len(r.errors) == 0 {
  269. // No more errors to inject, everything should succeed.
  270. return nil
  271. }
  272. for i, expected := range r.errors {
  273. klog.V(4).Infof("trying to match %q %q with %q %q", expected.Verb, expected.Resource, action.GetVerb(), action.GetResource())
  274. if action.Matches(expected.Verb, expected.Resource) {
  275. // That's the action we're waiting for, remove it from injectedErrors
  276. r.errors = append(r.errors[:i], r.errors[i+1:]...)
  277. klog.V(4).Infof("reactor found matching error at index %d: %q %q, returning %v", i, expected.Verb, expected.Resource, expected.Error)
  278. return expected.Error
  279. }
  280. }
  281. return nil
  282. }
  283. // CheckVolumes compares all expectedVolumes with set of volumes at the end of
  284. // the test and reports differences.
  285. func (r *VolumeReactor) CheckVolumes(expectedVolumes []*v1.PersistentVolume) error {
  286. r.lock.Lock()
  287. defer r.lock.Unlock()
  288. expectedMap := make(map[string]*v1.PersistentVolume)
  289. gotMap := make(map[string]*v1.PersistentVolume)
  290. // Clear any ResourceVersion from both sets
  291. for _, v := range expectedVolumes {
  292. // Don't modify the existing object
  293. v := v.DeepCopy()
  294. v.ResourceVersion = ""
  295. if v.Spec.ClaimRef != nil {
  296. v.Spec.ClaimRef.ResourceVersion = ""
  297. }
  298. expectedMap[v.Name] = v
  299. }
  300. for _, v := range r.volumes {
  301. // We must clone the volume because of golang race check - it was
  302. // written by the controller without any locks on it.
  303. v := v.DeepCopy()
  304. v.ResourceVersion = ""
  305. if v.Spec.ClaimRef != nil {
  306. v.Spec.ClaimRef.ResourceVersion = ""
  307. }
  308. gotMap[v.Name] = v
  309. }
  310. if !reflect.DeepEqual(expectedMap, gotMap) {
  311. // Print ugly but useful diff of expected and received objects for
  312. // easier debugging.
  313. return fmt.Errorf("Volume check failed [A-expected, B-got]: %s", diff.ObjectDiff(expectedMap, gotMap))
  314. }
  315. return nil
  316. }
  317. // CheckClaims compares all expectedClaims with set of claims at the end of the
  318. // test and reports differences.
  319. func (r *VolumeReactor) CheckClaims(expectedClaims []*v1.PersistentVolumeClaim) error {
  320. r.lock.Lock()
  321. defer r.lock.Unlock()
  322. expectedMap := make(map[string]*v1.PersistentVolumeClaim)
  323. gotMap := make(map[string]*v1.PersistentVolumeClaim)
  324. for _, c := range expectedClaims {
  325. // Don't modify the existing object
  326. c = c.DeepCopy()
  327. c.ResourceVersion = ""
  328. expectedMap[c.Name] = c
  329. }
  330. for _, c := range r.claims {
  331. // We must clone the claim because of golang race check - it was
  332. // written by the controller without any locks on it.
  333. c = c.DeepCopy()
  334. c.ResourceVersion = ""
  335. gotMap[c.Name] = c
  336. }
  337. if !reflect.DeepEqual(expectedMap, gotMap) {
  338. // Print ugly but useful diff of expected and received objects for
  339. // easier debugging.
  340. return fmt.Errorf("Claim check failed [A-expected, B-got result]: %s", diff.ObjectDiff(expectedMap, gotMap))
  341. }
  342. return nil
  343. }
  344. // PopChange returns one recorded updated object, either *v1.PersistentVolume
  345. // or *v1.PersistentVolumeClaim. Returns nil when there are no changes.
  346. func (r *VolumeReactor) PopChange() interface{} {
  347. r.lock.Lock()
  348. defer r.lock.Unlock()
  349. if len(r.changedObjects) == 0 {
  350. return nil
  351. }
  352. // For debugging purposes, print the queue
  353. for _, obj := range r.changedObjects {
  354. switch obj.(type) {
  355. case *v1.PersistentVolume:
  356. vol, _ := obj.(*v1.PersistentVolume)
  357. klog.V(4).Infof("reactor queue: %s", vol.Name)
  358. case *v1.PersistentVolumeClaim:
  359. claim, _ := obj.(*v1.PersistentVolumeClaim)
  360. klog.V(4).Infof("reactor queue: %s", claim.Name)
  361. }
  362. }
  363. // Pop the first item from the queue and return it
  364. obj := r.changedObjects[0]
  365. r.changedObjects = r.changedObjects[1:]
  366. return obj
  367. }
  368. // SyncAll simulates the controller periodic sync of volumes and claim. It
  369. // simply adds all these objects to the internal queue of updates. This method
  370. // should be used when the test manually calls syncClaim/syncVolume. Test that
  371. // use real controller loop (ctrl.Run()) will get periodic sync automatically.
  372. func (r *VolumeReactor) SyncAll() {
  373. r.lock.Lock()
  374. defer r.lock.Unlock()
  375. for _, c := range r.claims {
  376. r.changedObjects = append(r.changedObjects, c)
  377. }
  378. for _, v := range r.volumes {
  379. r.changedObjects = append(r.changedObjects, v)
  380. }
  381. r.changedSinceLastSync = 0
  382. }
  383. // GetChangeCount returns changes since last sync.
  384. func (r *VolumeReactor) GetChangeCount() int {
  385. r.lock.Lock()
  386. defer r.lock.Unlock()
  387. return r.changedSinceLastSync
  388. }
  389. // DeleteVolumeEvent simulates that a volume has been deleted in etcd and
  390. // the controller receives 'volume deleted' event.
  391. func (r *VolumeReactor) DeleteVolumeEvent(volume *v1.PersistentVolume) {
  392. r.lock.Lock()
  393. defer r.lock.Unlock()
  394. // Remove the volume from list of resulting volumes.
  395. delete(r.volumes, volume.Name)
  396. // Generate deletion event. Cloned volume is needed to prevent races (and we
  397. // would get a clone from etcd too).
  398. if r.fakeVolumeWatch != nil {
  399. r.fakeVolumeWatch.Delete(volume.DeepCopy())
  400. }
  401. }
  402. // DeleteClaimEvent simulates that a claim has been deleted in etcd and the
  403. // controller receives 'claim deleted' event.
  404. func (r *VolumeReactor) DeleteClaimEvent(claim *v1.PersistentVolumeClaim) {
  405. r.lock.Lock()
  406. defer r.lock.Unlock()
  407. // Remove the claim from list of resulting claims.
  408. delete(r.claims, claim.Name)
  409. // Generate deletion event. Cloned volume is needed to prevent races (and we
  410. // would get a clone from etcd too).
  411. if r.fakeClaimWatch != nil {
  412. r.fakeClaimWatch.Delete(claim.DeepCopy())
  413. }
  414. }
  415. // addVolumeEvent simulates that a volume has been added in etcd and the
  416. // controller receives 'volume added' event.
  417. func (r *VolumeReactor) addVolumeEvent(volume *v1.PersistentVolume) {
  418. r.lock.Lock()
  419. defer r.lock.Unlock()
  420. r.volumes[volume.Name] = volume
  421. // Generate event. No cloning is needed, this claim is not stored in the
  422. // controller cache yet.
  423. if r.fakeVolumeWatch != nil {
  424. r.fakeVolumeWatch.Add(volume)
  425. }
  426. }
  427. // modifyVolumeEvent simulates that a volume has been modified in etcd and the
  428. // controller receives 'volume modified' event.
  429. func (r *VolumeReactor) modifyVolumeEvent(volume *v1.PersistentVolume) {
  430. r.lock.Lock()
  431. defer r.lock.Unlock()
  432. r.volumes[volume.Name] = volume
  433. // Generate deletion event. Cloned volume is needed to prevent races (and we
  434. // would get a clone from etcd too).
  435. if r.fakeVolumeWatch != nil {
  436. r.fakeVolumeWatch.Modify(volume.DeepCopy())
  437. }
  438. }
  439. // AddClaimEvent simulates that a claim has been deleted in etcd and the
  440. // controller receives 'claim added' event.
  441. func (r *VolumeReactor) AddClaimEvent(claim *v1.PersistentVolumeClaim) {
  442. r.lock.Lock()
  443. defer r.lock.Unlock()
  444. r.claims[claim.Name] = claim
  445. // Generate event. No cloning is needed, this claim is not stored in the
  446. // controller cache yet.
  447. if r.fakeClaimWatch != nil {
  448. r.fakeClaimWatch.Add(claim)
  449. }
  450. }
  451. // AddClaims adds PVCs into VolumeReactor.
  452. func (r *VolumeReactor) AddClaims(claims []*v1.PersistentVolumeClaim) {
  453. r.lock.Lock()
  454. defer r.lock.Unlock()
  455. for _, claim := range claims {
  456. r.claims[claim.Name] = claim
  457. }
  458. }
  459. // AddVolumes adds PVs into VolumeReactor.
  460. func (r *VolumeReactor) AddVolumes(volumes []*v1.PersistentVolume) {
  461. r.lock.Lock()
  462. defer r.lock.Unlock()
  463. for _, volume := range volumes {
  464. r.volumes[volume.Name] = volume
  465. }
  466. }
  467. // AddClaim adds a PVC into VolumeReactor.
  468. func (r *VolumeReactor) AddClaim(claim *v1.PersistentVolumeClaim) {
  469. r.lock.Lock()
  470. defer r.lock.Unlock()
  471. r.claims[claim.Name] = claim
  472. }
  473. // AddVolume adds a PV into VolumeReactor.
  474. func (r *VolumeReactor) AddVolume(volume *v1.PersistentVolume) {
  475. r.lock.Lock()
  476. defer r.lock.Unlock()
  477. r.volumes[volume.Name] = volume
  478. }
  479. // DeleteVolume deletes a PV by name.
  480. func (r *VolumeReactor) DeleteVolume(name string) {
  481. r.lock.Lock()
  482. defer r.lock.Unlock()
  483. delete(r.volumes, name)
  484. }
  485. // AddClaimBoundToVolume adds a PVC and binds it to corresponding PV.
  486. func (r *VolumeReactor) AddClaimBoundToVolume(claim *v1.PersistentVolumeClaim) {
  487. r.lock.Lock()
  488. defer r.lock.Unlock()
  489. r.claims[claim.Name] = claim
  490. if volume, ok := r.volumes[claim.Spec.VolumeName]; ok {
  491. volume.Status.Phase = v1.VolumeBound
  492. }
  493. }
  494. // MarkVolumeAvaiable marks a PV available by name.
  495. func (r *VolumeReactor) MarkVolumeAvaiable(name string) {
  496. r.lock.Lock()
  497. defer r.lock.Unlock()
  498. if volume, ok := r.volumes[name]; ok {
  499. volume.Spec.ClaimRef = nil
  500. volume.Status.Phase = v1.VolumeAvailable
  501. volume.Annotations = nil
  502. }
  503. }
  504. // NewVolumeReactor creates a volume reactor.
  505. func NewVolumeReactor(client *fake.Clientset, fakeVolumeWatch, fakeClaimWatch *watch.FakeWatcher, errors []ReactorError) *VolumeReactor {
  506. reactor := &VolumeReactor{
  507. volumes: make(map[string]*v1.PersistentVolume),
  508. claims: make(map[string]*v1.PersistentVolumeClaim),
  509. fakeVolumeWatch: fakeVolumeWatch,
  510. fakeClaimWatch: fakeClaimWatch,
  511. errors: errors,
  512. watchers: make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher),
  513. }
  514. client.AddReactor("create", "persistentvolumes", reactor.React)
  515. client.AddReactor("create", "persistentvolumeclaims", reactor.React)
  516. client.AddReactor("update", "persistentvolumes", reactor.React)
  517. client.AddReactor("update", "persistentvolumeclaims", reactor.React)
  518. client.AddReactor("get", "persistentvolumes", reactor.React)
  519. client.AddReactor("get", "persistentvolumeclaims", reactor.React)
  520. client.AddReactor("delete", "persistentvolumes", reactor.React)
  521. client.AddReactor("delete", "persistentvolumeclaims", reactor.React)
  522. return reactor
  523. }