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 }