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

     1  package db
     2  
     3  import (
     4  	"database/sql"
     5  	"encoding/json"
     6  	"time"
     7  
     8  	"code.cloudfoundry.org/lager"
     9  	sq "github.com/Masterminds/squirrel"
    10  	"github.com/pf-qiu/concourse/v6/atc"
    11  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    12  )
    13  
    14  //go:generate counterfeiter . ResourceConfigScope
    15  
    16  // ResourceConfigScope represents the relationship between a possible pipeline resource and a resource config.
    17  // When a resource is specified to have a unique version history either through its base resource type or its custom
    18  // resource type, it results in its generated resource config to be scoped to the resource. This relationship is
    19  // translated into its row in the resource config scopes table to have both the resource id and resource config id
    20  // populated. When a resource has a shared version history, its resource config is not scoped to the (or any) resource
    21  // and its row in the resource config scopes table will have the resource config id populated but a NULL value for
    22  // the resource id. Resource versions will therefore be directly dependent on a resource config scope.
    23  type ResourceConfigScope interface {
    24  	ID() int
    25  	Resource() Resource
    26  	ResourceConfig() ResourceConfig
    27  
    28  	SaveVersions(SpanContext, []atc.Version) error
    29  	FindVersion(atc.Version) (ResourceConfigVersion, bool, error)
    30  	LatestVersion() (ResourceConfigVersion, bool, error)
    31  
    32  	AcquireResourceCheckingLock(
    33  		logger lager.Logger,
    34  	) (lock.Lock, bool, error)
    35  
    36  	UpdateLastCheckStartTime() (bool, error)
    37  
    38  	LastCheckEndTime() (time.Time, error)
    39  	UpdateLastCheckEndTime() (bool, error)
    40  }
    41  
    42  type resourceConfigScope struct {
    43  	id             int
    44  	resource       Resource
    45  	resourceConfig ResourceConfig
    46  
    47  	conn        Conn
    48  	lockFactory lock.LockFactory
    49  }
    50  
    51  func (r *resourceConfigScope) ID() int                        { return r.id }
    52  func (r *resourceConfigScope) Resource() Resource             { return r.resource }
    53  func (r *resourceConfigScope) ResourceConfig() ResourceConfig { return r.resourceConfig }
    54  
    55  func (r *resourceConfigScope) LastCheckEndTime() (time.Time, error) {
    56  	var lastCheckEndTime time.Time
    57  	err := psql.Select("last_check_end_time").
    58  		From("resource_config_scopes").
    59  		Where(sq.Eq{"id": r.id}).
    60  		RunWith(r.conn).
    61  		QueryRow().
    62  		Scan(&lastCheckEndTime)
    63  	if err != nil {
    64  		return time.Time{}, err
    65  	}
    66  
    67  	return lastCheckEndTime, nil
    68  }
    69  
    70  // SaveVersions stores a list of version in the db for a resource config
    71  // Each version will also have its check order field updated and the
    72  // Cache index for pipelines using the resource config will be bumped.
    73  //
    74  // In the case of a check resource from an older version, the versions
    75  // that already exist in the DB will be re-ordered using
    76  // incrementCheckOrder to input the correct check order
    77  func (r *resourceConfigScope) SaveVersions(spanContext SpanContext, versions []atc.Version) error {
    78  	return saveVersions(r.conn, r.ID(), versions, spanContext)
    79  }
    80  
    81  func saveVersions(conn Conn, rcsID int, versions []atc.Version, spanContext SpanContext) error {
    82  	tx, err := conn.Begin()
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	defer Rollback(tx)
    88  
    89  	var containsNewVersion bool
    90  	for _, version := range versions {
    91  		newVersion, err := saveResourceVersion(tx, rcsID, version, nil, spanContext)
    92  		if err != nil {
    93  			return err
    94  		}
    95  
    96  		containsNewVersion = containsNewVersion || newVersion
    97  	}
    98  
    99  	if containsNewVersion {
   100  		// bump the check order of all the versions returned by the check if there
   101  		// is at least one new version within the set of returned versions
   102  		for _, version := range versions {
   103  			versionJSON, err := json.Marshal(version)
   104  			if err != nil {
   105  				return err
   106  			}
   107  
   108  			err = incrementCheckOrder(tx, rcsID, string(versionJSON))
   109  			if err != nil {
   110  				return err
   111  			}
   112  		}
   113  
   114  		err = requestScheduleForJobsUsingResourceConfigScope(tx, rcsID)
   115  		if err != nil {
   116  			return err
   117  		}
   118  	}
   119  
   120  	err = tx.Commit()
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func (r *resourceConfigScope) FindVersion(v atc.Version) (ResourceConfigVersion, bool, error) {
   129  	rcv := &resourceConfigVersion{
   130  		conn: r.conn,
   131  	}
   132  
   133  	versionByte, err := json.Marshal(v)
   134  	if err != nil {
   135  		return nil, false, err
   136  	}
   137  
   138  	row := resourceConfigVersionQuery.
   139  		Where(sq.Eq{
   140  			"v.resource_config_scope_id": r.id,
   141  		}).
   142  		Where(sq.Expr("v.version_md5 = md5(?)", versionByte)).
   143  		RunWith(r.conn).
   144  		QueryRow()
   145  
   146  	err = scanResourceConfigVersion(rcv, row)
   147  	if err != nil {
   148  		if err == sql.ErrNoRows {
   149  			return nil, false, nil
   150  		}
   151  		return nil, false, err
   152  	}
   153  
   154  	return rcv, true, nil
   155  }
   156  
   157  func (r *resourceConfigScope) LatestVersion() (ResourceConfigVersion, bool, error) {
   158  	rcv := &resourceConfigVersion{
   159  		conn: r.conn,
   160  	}
   161  
   162  	row := resourceConfigVersionQuery.
   163  		Where(sq.Eq{"v.resource_config_scope_id": r.id}).
   164  		OrderBy("v.check_order DESC").
   165  		Limit(1).
   166  		RunWith(r.conn).
   167  		QueryRow()
   168  
   169  	err := scanResourceConfigVersion(rcv, row)
   170  	if err != nil {
   171  		if err == sql.ErrNoRows {
   172  			return nil, false, nil
   173  		}
   174  		return nil, false, err
   175  	}
   176  
   177  	return rcv, true, nil
   178  }
   179  
   180  func (r *resourceConfigScope) AcquireResourceCheckingLock(
   181  	logger lager.Logger,
   182  ) (lock.Lock, bool, error) {
   183  	return r.lockFactory.Acquire(
   184  		logger,
   185  		lock.NewResourceConfigCheckingLockID(r.resourceConfig.ID()),
   186  	)
   187  }
   188  
   189  func (r *resourceConfigScope) UpdateLastCheckStartTime() (bool, error) {
   190  	tx, err := r.conn.Begin()
   191  	if err != nil {
   192  		return false, err
   193  	}
   194  
   195  	defer Rollback(tx)
   196  
   197  	updated, err := checkIfRowsUpdated(tx, `
   198  		UPDATE resource_config_scopes
   199  		SET last_check_start_time = now()
   200  		WHERE id = $1
   201  	`, r.id)
   202  	if err != nil {
   203  		return false, err
   204  	}
   205  
   206  	if !updated {
   207  		return false, nil
   208  	}
   209  
   210  	err = tx.Commit()
   211  	if err != nil {
   212  		return false, err
   213  	}
   214  
   215  	return true, nil
   216  }
   217  
   218  func (r *resourceConfigScope) UpdateLastCheckEndTime() (bool, error) {
   219  	tx, err := r.conn.Begin()
   220  	if err != nil {
   221  		return false, err
   222  	}
   223  
   224  	defer Rollback(tx)
   225  
   226  	updated, err := checkIfRowsUpdated(tx, `
   227  		UPDATE resource_config_scopes
   228  		SET last_check_end_time = now()
   229  		WHERE id = $1
   230  	`, r.id)
   231  	if err != nil {
   232  		return false, err
   233  	}
   234  
   235  	if !updated {
   236  		return false, nil
   237  	}
   238  
   239  	err = tx.Commit()
   240  	if err != nil {
   241  		return false, err
   242  	}
   243  
   244  	return true, nil
   245  }
   246  
   247  func saveResourceVersion(tx Tx, rcsID int, version atc.Version, metadata ResourceConfigMetadataFields, spanContext SpanContext) (bool, error) {
   248  	versionJSON, err := json.Marshal(version)
   249  	if err != nil {
   250  		return false, err
   251  	}
   252  
   253  	metadataJSON, err := json.Marshal(metadata)
   254  	if err != nil {
   255  		return false, err
   256  	}
   257  
   258  	spanContextJSON, err := json.Marshal(spanContext)
   259  	if err != nil {
   260  		return false, err
   261  	}
   262  
   263  	var checkOrder int
   264  	err = tx.QueryRow(`
   265  		INSERT INTO resource_config_versions (resource_config_scope_id, version, version_md5, metadata, span_context)
   266  		SELECT $1, $2, md5($3), $4, $5
   267  		ON CONFLICT (resource_config_scope_id, version_md5)
   268  		DO UPDATE SET metadata = COALESCE(NULLIF(excluded.metadata, 'null'::jsonb), resource_config_versions.metadata)
   269  		RETURNING check_order
   270  		`, rcsID, string(versionJSON), string(versionJSON), string(metadataJSON), string(spanContextJSON)).Scan(&checkOrder)
   271  	if err != nil {
   272  		return false, err
   273  	}
   274  
   275  	return checkOrder == 0, nil
   276  }
   277  
   278  // increment the check order if the version's check order is less than the
   279  // current max. This will fix the case of a check from an old version causing
   280  // the desired order to change; existing versions will be re-ordered since
   281  // we add them in the desired order.
   282  func incrementCheckOrder(tx Tx, rcsID int, version string) error {
   283  	_, err := tx.Exec(`
   284  		WITH max_checkorder AS (
   285  			SELECT max(check_order) co
   286  			FROM resource_config_versions
   287  			WHERE resource_config_scope_id = $1
   288  		)
   289  
   290  		UPDATE resource_config_versions
   291  		SET check_order = mc.co + 1
   292  		FROM max_checkorder mc
   293  		WHERE resource_config_scope_id = $1
   294  		AND version_md5 = md5($2)
   295  		AND check_order <= mc.co;`, rcsID, version)
   296  	return err
   297  }
   298  
   299  // The SELECT query orders the jobs for updating to prevent deadlocking.
   300  // Updating multiple rows using a SELECT subquery does not preserve the same
   301  // order for the updates, which can lead to deadlocking.
   302  func requestScheduleForJobsUsingResourceConfigScope(tx Tx, rcsID int) error {
   303  	rows, err := psql.Select("DISTINCT j.job_id").
   304  		From("job_inputs j").
   305  		Join("resources r ON r.id = j.resource_id").
   306  		Where(sq.Eq{
   307  			"r.resource_config_scope_id": rcsID,
   308  			"j.passed_job_id":            nil,
   309  		}).
   310  		OrderBy("j.job_id DESC").
   311  		RunWith(tx).
   312  		Query()
   313  	if err != nil {
   314  		return err
   315  	}
   316  
   317  	var jobIDs []int
   318  	for rows.Next() {
   319  		var id int
   320  		err = rows.Scan(&id)
   321  		if err != nil {
   322  			return err
   323  		}
   324  
   325  		jobIDs = append(jobIDs, id)
   326  	}
   327  
   328  	for _, jID := range jobIDs {
   329  		_, err := psql.Update("jobs").
   330  			Set("schedule_requested", sq.Expr("now()")).
   331  			Where(sq.Eq{
   332  				"id": jID,
   333  			}).
   334  			RunWith(tx).
   335  			Exec()
   336  		if err != nil {
   337  			return err
   338  		}
   339  	}
   340  
   341  	return nil
   342  }