github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/resource_cache.go (about)

     1  package db
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  
    10  	sq "github.com/Masterminds/squirrel"
    11  	"github.com/pf-qiu/concourse/v6/atc"
    12  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    13  )
    14  
    15  var ErrResourceCacheAlreadyExists = errors.New("resource-cache-already-exists")
    16  var ErrResourceCacheDisappeared = errors.New("resource-cache-disappeared")
    17  
    18  // ResourceCache represents an instance of a ResourceConfig's version.
    19  //
    20  // A ResourceCache is created by a `get`, an `image_resource`, or a resource
    21  // type in a pipeline.
    22  //
    23  // ResourceCaches are garbage-collected by gc.ResourceCacheCollector.
    24  type ResourceCacheDescriptor struct {
    25  	ResourceConfigDescriptor ResourceConfigDescriptor // The resource configuration.
    26  	Version                  atc.Version              // The version of the resource.
    27  	Params                   atc.Params               // The params used when fetching the version.
    28  }
    29  
    30  func (cache *ResourceCacheDescriptor) find(tx Tx, lockFactory lock.LockFactory, conn Conn) (UsedResourceCache, bool, error) {
    31  	resourceConfig, found, err := cache.ResourceConfigDescriptor.find(tx, lockFactory, conn)
    32  	if err != nil {
    33  		return nil, false, err
    34  	}
    35  
    36  	if !found {
    37  		return nil, false, nil
    38  	}
    39  
    40  	return cache.findWithResourceConfig(tx, resourceConfig, lockFactory, conn)
    41  }
    42  
    43  func (cache *ResourceCacheDescriptor) findOrCreate(
    44  	tx Tx,
    45  	lockFactory lock.LockFactory,
    46  	conn Conn,
    47  ) (UsedResourceCache, error) {
    48  	resourceConfig, err := cache.ResourceConfigDescriptor.findOrCreate(tx, lockFactory, conn)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	rc, found, err := cache.findWithResourceConfig(tx, resourceConfig, lockFactory, conn)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	if !found {
    59  		var id int
    60  		err = psql.Insert("resource_caches").
    61  			Columns(
    62  				"resource_config_id",
    63  				"version",
    64  				"version_md5",
    65  				"params_hash",
    66  			).
    67  			Values(
    68  				resourceConfig.ID(),
    69  				cache.version(),
    70  				sq.Expr("md5(?)", cache.version()),
    71  				paramsHash(cache.Params),
    72  			).
    73  			Suffix(`
    74  				ON CONFLICT (resource_config_id, version_md5, params_hash) DO UPDATE SET
    75  				resource_config_id = EXCLUDED.resource_config_id,
    76  				version = EXCLUDED.version,
    77  				version_md5 = EXCLUDED.version_md5,
    78  				params_hash = EXCLUDED.params_hash
    79  				RETURNING id
    80  			`).
    81  			RunWith(tx).
    82  			QueryRow().
    83  			Scan(&id)
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  
    88  		rc = &usedResourceCache{
    89  			id:             id,
    90  			version:        cache.Version,
    91  			resourceConfig: resourceConfig,
    92  			lockFactory:    lockFactory,
    93  			conn:           conn,
    94  		}
    95  	}
    96  
    97  	return rc, nil
    98  }
    99  
   100  func (cache *ResourceCacheDescriptor) use(
   101  	tx Tx,
   102  	rc UsedResourceCache,
   103  	user ResourceCacheUser,
   104  ) error {
   105  	cols := user.SQLMap()
   106  	cols["resource_cache_id"] = rc.ID()
   107  
   108  	var resourceCacheUseExists int
   109  	err := psql.Select("1").
   110  		From("resource_cache_uses").
   111  		Where(sq.Eq(cols)).
   112  		RunWith(tx).
   113  		QueryRow().
   114  		Scan(&resourceCacheUseExists)
   115  	if err != nil {
   116  		if err != sql.ErrNoRows {
   117  			return err
   118  		}
   119  	}
   120  
   121  	if err == nil {
   122  		// use already exists
   123  		return nil
   124  	}
   125  
   126  	_, err = psql.Insert("resource_cache_uses").
   127  		SetMap(cols).
   128  		RunWith(tx).
   129  		Exec()
   130  	return err
   131  }
   132  
   133  func (cache *ResourceCacheDescriptor) findWithResourceConfig(tx Tx, resourceConfig ResourceConfig, lockFactory lock.LockFactory, conn Conn) (UsedResourceCache, bool, error) {
   134  	var id int
   135  	err := psql.Select("id").
   136  		From("resource_caches").
   137  		Where(sq.Eq{
   138  			"resource_config_id": resourceConfig.ID(),
   139  			"params_hash":        paramsHash(cache.Params),
   140  		}).
   141  		Where(sq.Expr("version_md5 = md5(?)", cache.version())).
   142  		Suffix("FOR SHARE").
   143  		RunWith(tx).
   144  		QueryRow().
   145  		Scan(&id)
   146  	if err != nil {
   147  		if err == sql.ErrNoRows {
   148  			return nil, false, nil
   149  		}
   150  
   151  		return nil, false, err
   152  	}
   153  
   154  	return &usedResourceCache{
   155  		id:             id,
   156  		version:        cache.Version,
   157  		resourceConfig: resourceConfig,
   158  		lockFactory:    lockFactory,
   159  		conn:           conn,
   160  	}, true, nil
   161  }
   162  
   163  func (cache *ResourceCacheDescriptor) version() string {
   164  	j, _ := json.Marshal(cache.Version)
   165  	return string(j)
   166  }
   167  
   168  func paramsHash(p atc.Params) string {
   169  	if p != nil {
   170  		return mapHash(p)
   171  	}
   172  
   173  	return mapHash(atc.Params{})
   174  }
   175  
   176  // UsedResourceCache is created whenever a ResourceCache is Created and/or
   177  // Used.
   178  //
   179  // So long as the UsedResourceCache exists, the underlying ResourceCache can
   180  // not be removed.
   181  //
   182  // UsedResourceCaches become unused by the gc.ResourceCacheCollector, which may
   183  // then lead to the ResourceCache being garbage-collected.
   184  //
   185  // See FindOrCreateForBuild, FindOrCreateForResource, and
   186  // FindOrCreateForResourceType for more information on when it becomes unused.
   187  
   188  //go:generate counterfeiter . UsedResourceCache
   189  
   190  type UsedResourceCache interface {
   191  	ID() int
   192  	Version() atc.Version
   193  
   194  	ResourceConfig() ResourceConfig
   195  
   196  	Destroy(Tx) (bool, error)
   197  	BaseResourceType() *UsedBaseResourceType
   198  }
   199  
   200  type usedResourceCache struct {
   201  	id             int
   202  	resourceConfig ResourceConfig
   203  	version        atc.Version
   204  
   205  	lockFactory lock.LockFactory
   206  	conn        Conn
   207  }
   208  
   209  func (cache *usedResourceCache) ID() int                        { return cache.id }
   210  func (cache *usedResourceCache) ResourceConfig() ResourceConfig { return cache.resourceConfig }
   211  func (cache *usedResourceCache) Version() atc.Version           { return cache.version }
   212  
   213  func (cache *usedResourceCache) Destroy(tx Tx) (bool, error) {
   214  	rows, err := psql.Delete("resource_caches").
   215  		Where(sq.Eq{
   216  			"id": cache.id,
   217  		}).
   218  		RunWith(tx).
   219  		Exec()
   220  	if err != nil {
   221  		return false, err
   222  	}
   223  
   224  	affected, err := rows.RowsAffected()
   225  	if err != nil {
   226  		return false, err
   227  	}
   228  
   229  	if affected == 0 {
   230  		return false, ErrResourceCacheDisappeared
   231  	}
   232  
   233  	return true, nil
   234  }
   235  
   236  func (cache *usedResourceCache) BaseResourceType() *UsedBaseResourceType {
   237  	if cache.resourceConfig.CreatedByBaseResourceType() != nil {
   238  		return cache.resourceConfig.CreatedByBaseResourceType()
   239  	}
   240  
   241  	return cache.resourceConfig.CreatedByResourceCache().BaseResourceType()
   242  }
   243  
   244  func mapHash(m map[string]interface{}) string {
   245  	j, _ := json.Marshal(m)
   246  	return fmt.Sprintf("%x", sha256.Sum256(j))
   247  }