github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/resource_cache_factory_test.go (about) 1 package db_test 2 3 import ( 4 "crypto/md5" 5 "crypto/sha256" 6 "database/sql" 7 "fmt" 8 "sync" 9 "time" 10 11 "code.cloudfoundry.org/lager/lagertest" 12 13 "github.com/pf-qiu/concourse/v6/atc" 14 "github.com/pf-qiu/concourse/v6/atc/db" 15 16 . "github.com/onsi/ginkgo" 17 . "github.com/onsi/gomega" 18 ) 19 20 var _ = Describe("ResourceCacheFactory", func() { 21 var ( 22 usedImageBaseResourceType *db.UsedBaseResourceType 23 24 resourceCacheLifecycle db.ResourceCacheLifecycle 25 26 resourceType1 atc.VersionedResourceType 27 resourceType2 atc.VersionedResourceType 28 resourceType3 atc.VersionedResourceType 29 resourceTypeUsingBogusBaseType atc.VersionedResourceType 30 resourceTypeOverridingBaseType atc.VersionedResourceType 31 32 logger *lagertest.TestLogger 33 build db.Build 34 err error 35 ) 36 37 BeforeEach(func() { 38 build, err = defaultTeam.CreateOneOffBuild() 39 Expect(err).NotTo(HaveOccurred()) 40 }) 41 42 Describe("FindOrCreateResourceCache", func() { 43 BeforeEach(func() { 44 setupTx, err := dbConn.Begin() 45 Expect(err).ToNot(HaveOccurred()) 46 47 baseResourceType := db.BaseResourceType{ 48 Name: "some-base-type", 49 } 50 51 _, err = baseResourceType.FindOrCreate(setupTx, false) 52 Expect(err).NotTo(HaveOccurred()) 53 54 imageBaseResourceType := db.BaseResourceType{ 55 Name: "some-image-type", 56 } 57 58 resourceCacheLifecycle = db.NewResourceCacheLifecycle(dbConn) 59 60 usedImageBaseResourceType, err = imageBaseResourceType.FindOrCreate(setupTx, false) 61 Expect(err).NotTo(HaveOccurred()) 62 63 Expect(setupTx.Commit()).To(Succeed()) 64 65 resourceType1 = atc.VersionedResourceType{ 66 ResourceType: atc.ResourceType{ 67 Name: "some-type", 68 Type: "some-type-type", 69 Source: atc.Source{ 70 "some-type": "source", 71 }, 72 }, 73 Version: atc.Version{"some-type": "version"}, 74 } 75 76 resourceType2 = atc.VersionedResourceType{ 77 ResourceType: atc.ResourceType{ 78 Name: "some-type-type", 79 Type: "some-base-type", 80 Source: atc.Source{ 81 "some-type-type": "some-secret-sauce", 82 }, 83 }, 84 Version: atc.Version{"some-type-type": "version"}, 85 } 86 87 resourceType3 = atc.VersionedResourceType{ 88 ResourceType: atc.ResourceType{ 89 Name: "some-unused-type", 90 Type: "some-base-type", 91 Source: atc.Source{ 92 "some-unused-type": "source", 93 }, 94 }, 95 Version: atc.Version{"some-unused-type": "version"}, 96 } 97 98 resourceTypeUsingBogusBaseType = atc.VersionedResourceType{ 99 ResourceType: atc.ResourceType{ 100 Name: "some-type-using-bogus-base-type", 101 Type: "some-bogus-base-type", 102 Source: atc.Source{ 103 "some-type-using-bogus-base-type": "source", 104 }, 105 }, 106 Version: atc.Version{"some-type-using-bogus-base-type": "version"}, 107 } 108 109 resourceTypeOverridingBaseType = atc.VersionedResourceType{ 110 ResourceType: atc.ResourceType{ 111 Name: "some-image-type", 112 Type: "some-image-type", 113 Source: atc.Source{ 114 "some-image-type": "source", 115 }, 116 }, 117 Version: atc.Version{"some-image-type": "version"}, 118 } 119 120 logger = lagertest.NewTestLogger("test") 121 }) 122 123 It("creates resource cache in database", func() { 124 usedResourceCache, err := resourceCacheFactory.FindOrCreateResourceCache( 125 db.ForBuild(build.ID()), 126 "some-type", 127 atc.Version{"some": "version"}, 128 atc.Source{ 129 "some": "source", 130 }, 131 atc.Params{"some": "params"}, 132 atc.VersionedResourceTypes{ 133 resourceType1, 134 resourceType2, 135 resourceType3, 136 }, 137 ) 138 Expect(err).ToNot(HaveOccurred()) 139 Expect(usedResourceCache.Version()).To(Equal(atc.Version{"some": "version"})) 140 141 rows, err := psql.Select("a.version, a.version_md5, a.params_hash, o.source_hash, b.name"). 142 From("resource_caches a"). 143 LeftJoin("resource_configs o ON a.resource_config_id = o.id"). 144 LeftJoin("base_resource_types b ON o.base_resource_type_id = b.id"). 145 RunWith(dbConn). 146 Query() 147 Expect(err).NotTo(HaveOccurred()) 148 resourceCaches := []resourceCache{} 149 for rows.Next() { 150 var version string 151 var versionMd5 string 152 var paramsHash string 153 var sourceHash sql.NullString 154 var baseResourceTypeName sql.NullString 155 156 err := rows.Scan(&version, &versionMd5, ¶msHash, &sourceHash, &baseResourceTypeName) 157 Expect(err).NotTo(HaveOccurred()) 158 159 var sourceHashString string 160 if sourceHash.Valid { 161 sourceHashString = sourceHash.String 162 } 163 164 var baseResourceTypeNameString string 165 if baseResourceTypeName.Valid { 166 baseResourceTypeNameString = baseResourceTypeName.String 167 } 168 169 resourceCaches = append(resourceCaches, resourceCache{ 170 Version: version, 171 VersionMd5: versionMd5, 172 ParamsHash: paramsHash, 173 SourceHash: sourceHashString, 174 BaseResourceName: baseResourceTypeNameString, 175 }) 176 } 177 178 var toHash = func(s string) string { 179 return fmt.Sprintf("%x", sha256.Sum256([]byte(s))) 180 } 181 182 var toMd5 = func(s string) string { 183 return fmt.Sprintf("%x", md5.Sum([]byte(s))) 184 } 185 186 Expect(resourceCaches).To(ConsistOf( 187 resourceCache{ 188 Version: `{"some-type-type": "version"}`, 189 VersionMd5: toMd5(`{"some-type-type":"version"}`), 190 ParamsHash: toHash(`{}`), 191 BaseResourceName: "some-base-type", 192 SourceHash: toHash(`{"some-type-type":"some-secret-sauce"}`), 193 }, 194 resourceCache{ 195 Version: `{"some-type": "version"}`, 196 VersionMd5: toMd5(`{"some-type":"version"}`), 197 ParamsHash: toHash(`{}`), 198 SourceHash: toHash(`{"some-type":"source"}`), 199 }, 200 resourceCache{ 201 Version: `{"some": "version"}`, 202 VersionMd5: toMd5(`{"some":"version"}`), 203 ParamsHash: toHash(`{"some":"params"}`), 204 SourceHash: toHash(`{"some":"source"}`), 205 }, 206 )) 207 }) 208 209 It("returns an error if base resource type does not exist", func() { 210 _, err := resourceCacheFactory.FindOrCreateResourceCache( 211 db.ForBuild(build.ID()), 212 "some-type-using-bogus-base-type", 213 atc.Version{"some": "version"}, 214 atc.Source{ 215 "some": "source", 216 }, 217 atc.Params{"some": "params"}, 218 atc.VersionedResourceTypes{ 219 resourceType1, 220 resourceTypeUsingBogusBaseType, 221 }, 222 ) 223 Expect(err).To(HaveOccurred()) 224 Expect(err).To(Equal(db.BaseResourceTypeNotFoundError{Name: "some-bogus-base-type"})) 225 }) 226 227 It("allows a base resource type to be overridden using itself", func() { 228 usedResourceCache, err := resourceCacheFactory.FindOrCreateResourceCache( 229 db.ForBuild(build.ID()), 230 "some-image-type", 231 atc.Version{"some": "version"}, 232 atc.Source{ 233 "some": "source", 234 }, 235 atc.Params{"some": "params"}, 236 atc.VersionedResourceTypes{ 237 resourceTypeOverridingBaseType, 238 }, 239 ) 240 Expect(err).ToNot(HaveOccurred()) 241 242 Expect(usedResourceCache.ResourceConfig().CreatedByResourceCache().ResourceConfig().CreatedByBaseResourceType().ID).To(Equal(usedImageBaseResourceType.ID)) 243 }) 244 245 Context("when the resource cache is concurrently deleted and created", func() { 246 BeforeEach(func() { 247 Expect(build.Finish(db.BuildStatusSucceeded)).To(Succeed()) 248 Expect(build.SetInterceptible(false)).To(Succeed()) 249 }) 250 251 It("consistently is able to be used", func() { 252 // enable concurrent use of database. this is set to 1 by default to 253 // ensure methods don't require more than one in a single connection, 254 // which can cause deadlocking as the pool is limited. 255 dbConn.SetMaxOpenConns(10) 256 257 done := make(chan struct{}) 258 259 wg := new(sync.WaitGroup) 260 for i := 0; i < 5; i++ { 261 wg.Add(1) 262 go func() { 263 defer GinkgoRecover() 264 defer wg.Done() 265 266 for { 267 select { 268 case <-done: 269 return 270 default: 271 Expect(resourceCacheLifecycle.CleanUsesForFinishedBuilds(logger)).To(Succeed()) 272 Expect(resourceCacheLifecycle.CleanUpInvalidCaches(logger)).To(Succeed()) 273 Expect(resourceConfigFactory.CleanUnreferencedConfigs(0)).To(Succeed()) 274 } 275 } 276 }() 277 } 278 279 wg.Add(1) 280 go func() { 281 defer GinkgoRecover() 282 defer close(done) 283 defer wg.Done() 284 285 for i := 0; i < 100; i++ { 286 _, err := resourceCacheFactory.FindOrCreateResourceCache( 287 db.ForBuild(build.ID()), 288 "some-base-resource-type", 289 atc.Version{"some": "version"}, 290 atc.Source{"some": "source"}, 291 atc.Params{"some": "params"}, 292 atc.VersionedResourceTypes{}, 293 ) 294 Expect(err).ToNot(HaveOccurred()) 295 } 296 }() 297 298 wg.Wait() 299 }) 300 }) 301 }) 302 303 Describe("FindResourceCacheByID", func() { 304 var resourceCacheUser db.ResourceCacheUser 305 var someUsedResourceCacheFromBaseResource db.UsedResourceCache 306 var someUsedResourceCacheFromCustomResource db.UsedResourceCache 307 BeforeEach(func() { 308 resourceCacheUser = db.ForBuild(build.ID()) 309 310 someUsedResourceCacheFromBaseResource, err = resourceCacheFactory.FindOrCreateResourceCache(resourceCacheUser, 311 "some-base-resource-type", 312 atc.Version{"some": "version"}, 313 atc.Source{ 314 "some": "source", 315 }, 316 atc.Params{"some": fmt.Sprintf("param-%d", time.Now().UnixNano())}, 317 atc.VersionedResourceTypes{}, 318 ) 319 Expect(err).ToNot(HaveOccurred()) 320 321 someUsedResourceCacheFromCustomResource, err = resourceCacheFactory.FindOrCreateResourceCache(resourceCacheUser, 322 "some-custom-resource-type", 323 atc.Version{"some": "version"}, 324 atc.Source{ 325 "some": "source", 326 }, 327 atc.Params{"some": fmt.Sprintf("param-%d", time.Now().UnixNano())}, 328 atc.VersionedResourceTypes{ 329 atc.VersionedResourceType{ 330 ResourceType: atc.ResourceType{ 331 Name: "some-custom-resource-type", 332 Type: "some-base-resource-type", 333 Source: atc.Source{ 334 "some": "source", 335 }, 336 }, 337 Version: atc.Version{"showme": "whatyougot"}, 338 }, 339 }, 340 ) 341 Expect(err).ToNot(HaveOccurred()) 342 }) 343 344 It("returns a UsedResourceCache from a BaseResource", func() { 345 actualUsedResourceCache, found, err := resourceCacheFactory.FindResourceCacheByID(someUsedResourceCacheFromBaseResource.ID()) 346 347 Expect(found).To(BeTrue()) 348 Expect(err).ToNot(HaveOccurred()) 349 Expect(actualUsedResourceCache.ID()).To(Equal(someUsedResourceCacheFromBaseResource.ID())) 350 Expect(actualUsedResourceCache.ResourceConfig().CreatedByBaseResourceType().Name).To(Equal("some-base-resource-type")) 351 Expect(actualUsedResourceCache.ResourceConfig().CreatedByResourceCache()).To(BeNil()) 352 }) 353 354 It("returns a UsedResourceCache from a custom ResourceCache", func() { 355 actualUsedResourceCache, found, err := resourceCacheFactory.FindResourceCacheByID(someUsedResourceCacheFromCustomResource.ID()) 356 357 Expect(found).To(BeTrue()) 358 Expect(err).ToNot(HaveOccurred()) 359 Expect(actualUsedResourceCache.ID()).To(Equal(someUsedResourceCacheFromCustomResource.ID())) 360 Expect(actualUsedResourceCache.ResourceConfig().CreatedByResourceCache().Version()).To(Equal(atc.Version{"showme": "whatyougot"})) 361 }) 362 363 It("returns !found when one is not found", func() { 364 _, found, err := resourceCacheFactory.FindResourceCacheByID(42) 365 366 Expect(found).To(BeFalse()) 367 Expect(err).ToNot(HaveOccurred()) 368 }) 369 }) 370 371 }) 372 373 type resourceCache struct { 374 Version string 375 VersionMd5 string 376 ParamsHash string 377 SourceHash string 378 BaseResourceName string 379 }