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, &paramsHash, &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  }