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

     1  package db
     2  
     3  import (
     4  	"database/sql"
     5  	"fmt"
     6  	"strconv"
     7  	"time"
     8  
     9  	sq "github.com/Masterminds/squirrel"
    10  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    11  )
    12  
    13  //go:generate counterfeiter . BuildFactory
    14  
    15  type BuildFactory interface {
    16  	Build(int) (Build, bool, error)
    17  	VisibleBuilds([]string, Page) ([]Build, Pagination, error)
    18  	AllBuilds(Page) ([]Build, Pagination, error)
    19  	PublicBuilds(Page) ([]Build, Pagination, error)
    20  	GetAllStartedBuilds() ([]Build, error)
    21  	GetDrainableBuilds() ([]Build, error)
    22  	// TODO: move to BuildLifecycle, new interface (see WorkerLifecycle)
    23  	MarkNonInterceptibleBuilds() error
    24  }
    25  
    26  type buildFactory struct {
    27  	conn              Conn
    28  	lockFactory       lock.LockFactory
    29  	oneOffGracePeriod time.Duration
    30  	failedGracePeriod time.Duration
    31  }
    32  
    33  func NewBuildFactory(conn Conn, lockFactory lock.LockFactory, oneOffGracePeriod time.Duration, failedGracePeriod time.Duration) BuildFactory {
    34  	return &buildFactory{
    35  		conn:              conn,
    36  		lockFactory:       lockFactory,
    37  		oneOffGracePeriod: oneOffGracePeriod,
    38  		failedGracePeriod: failedGracePeriod,
    39  	}
    40  }
    41  
    42  func (f *buildFactory) Build(buildID int) (Build, bool, error) {
    43  	build := newEmptyBuild(f.conn, f.lockFactory)
    44  	row := buildsQuery.
    45  		Where(sq.Eq{"b.id": buildID}).
    46  		RunWith(f.conn).
    47  		QueryRow()
    48  
    49  	err := scanBuild(build, row, f.conn.EncryptionStrategy())
    50  	if err != nil {
    51  		if err == sql.ErrNoRows {
    52  			return nil, false, nil
    53  		}
    54  		return nil, false, err
    55  	}
    56  
    57  	return build, true, nil
    58  }
    59  
    60  func (f *buildFactory) VisibleBuilds(teamNames []string, page Page) ([]Build, Pagination, error) {
    61  	newBuildsQuery := buildsQuery.
    62  		Where(sq.Or{
    63  			sq.Eq{"p.public": true},
    64  			sq.Eq{"t.name": teamNames},
    65  		})
    66  
    67  	if page.UseDate {
    68  		return getBuildsWithDates(newBuildsQuery, minMaxIdQuery, page, f.conn,
    69  			f.lockFactory)
    70  	}
    71  	return getBuildsWithPagination(newBuildsQuery, minMaxIdQuery, page, f.conn,
    72  		f.lockFactory)
    73  }
    74  
    75  func (f *buildFactory) AllBuilds(page Page) ([]Build, Pagination, error) {
    76  	if page.UseDate {
    77  		return getBuildsWithDates(buildsQuery, minMaxIdQuery, page, f.conn,
    78  			f.lockFactory)
    79  	}
    80  	return getBuildsWithPagination(buildsQuery, minMaxIdQuery,
    81  		page, f.conn, f.lockFactory)
    82  }
    83  
    84  func (f *buildFactory) PublicBuilds(page Page) ([]Build, Pagination, error) {
    85  	return getBuildsWithPagination(
    86  		buildsQuery.Where(sq.Eq{"p.public": true}), minMaxIdQuery,
    87  		page, f.conn, f.lockFactory)
    88  }
    89  
    90  func (f *buildFactory) MarkNonInterceptibleBuilds() error {
    91  	_, err := psql.Update("builds b").
    92  		Set("interceptible", false).
    93  		Where(sq.Eq{
    94  			"completed":     true,
    95  			"interceptible": true,
    96  		}).
    97  		Where(sq.Or{
    98  			sq.NotEq{"job_id": nil},
    99  			sq.Expr(fmt.Sprintf("now() - end_time > '%d seconds'::interval", int(f.oneOffGracePeriod.Seconds()))),
   100  		}).
   101  		Where(f.constructBuildFilter()).
   102  		RunWith(f.conn).
   103  		Exec()
   104  	return err
   105  }
   106  
   107  func (f *buildFactory) constructBuildFilter() sq.Or {
   108  	buildFilter := sq.Or{
   109  		sq.Expr("NOT EXISTS (SELECT 1 FROM jobs j WHERE j.latest_completed_build_id = b.id)"),
   110  		sq.Eq{"status": string(BuildStatusSucceeded)},
   111  	}
   112  	if f.failedGracePeriod > 0 { // if zero, grace period is disabled
   113  		buildFilter = append(buildFilter,
   114  			sq.Expr(fmt.Sprintf("now() - end_time > '%d seconds'::interval", int(f.failedGracePeriod.Seconds()))))
   115  	}
   116  	return buildFilter
   117  }
   118  
   119  func (f *buildFactory) GetDrainableBuilds() ([]Build, error) {
   120  	query := buildsQuery.Where(sq.Eq{
   121  		"b.completed": true,
   122  		"b.drained":   false,
   123  	})
   124  
   125  	return getBuilds(query, f.conn, f.lockFactory)
   126  }
   127  
   128  func (f *buildFactory) GetAllStartedBuilds() ([]Build, error) {
   129  	query := buildsQuery.Where(sq.Eq{
   130  		"b.status": BuildStatusStarted,
   131  	})
   132  
   133  	return getBuilds(query, f.conn, f.lockFactory)
   134  }
   135  
   136  func getBuilds(buildsQuery sq.SelectBuilder, conn Conn, lockFactory lock.LockFactory) ([]Build, error) {
   137  	rows, err := buildsQuery.RunWith(conn).Query()
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	defer Close(rows)
   143  
   144  	bs := []Build{}
   145  
   146  	for rows.Next() {
   147  		b := newEmptyBuild(conn, lockFactory)
   148  		err := scanBuild(b, rows, conn.EncryptionStrategy())
   149  		if err != nil {
   150  			return nil, err
   151  		}
   152  
   153  		bs = append(bs, b)
   154  	}
   155  
   156  	return bs, nil
   157  }
   158  
   159  func getBuildsWithDates(buildsQuery, minMaxIdQuery sq.SelectBuilder, page Page, conn Conn, lockFactory lock.LockFactory) ([]Build, Pagination, error) {
   160  	var newPage = Page{Limit: page.Limit}
   161  
   162  	tx, err := conn.Begin()
   163  	if err != nil {
   164  		return nil, Pagination{}, err
   165  	}
   166  
   167  	defer Rollback(tx)
   168  
   169  	if page.From != nil {
   170  		fromRow, err := buildsQuery.
   171  			Where(sq.Expr("b.start_time >= to_timestamp(" + strconv.Itoa(*page.From) + ")")).
   172  			OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC").
   173  			Limit(1).
   174  			RunWith(tx).
   175  			Query()
   176  
   177  		if err != nil {
   178  			// The user has no builds since that given time
   179  			if err == sql.ErrNoRows {
   180  				return []Build{}, Pagination{}, nil
   181  			}
   182  
   183  			return nil, Pagination{}, err
   184  		}
   185  
   186  		defer fromRow.Close()
   187  
   188  		found := false
   189  		for fromRow.Next() {
   190  			found = true
   191  			build := newEmptyBuild(conn, lockFactory)
   192  			err = scanBuild(build, fromRow, conn.EncryptionStrategy())
   193  			if err != nil {
   194  				return nil, Pagination{}, err
   195  			}
   196  
   197  			newPage.From = NewIntPtr(build.ID())
   198  		}
   199  		if !found {
   200  			return []Build{}, Pagination{}, nil
   201  		}
   202  	}
   203  
   204  	if page.To != nil {
   205  		untilRow, err := buildsQuery.
   206  			Where(sq.Expr("b.start_time <= to_timestamp(" + strconv.Itoa(*page.To) + ")")).
   207  			OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC").
   208  			Limit(1).
   209  			RunWith(tx).
   210  			Query()
   211  		if err != nil {
   212  			// The user has no builds since that given time
   213  			if err == sql.ErrNoRows {
   214  				return []Build{}, Pagination{}, nil
   215  			}
   216  		}
   217  
   218  		defer untilRow.Close()
   219  
   220  		found := false
   221  		for untilRow.Next() {
   222  			found = true
   223  			build := newEmptyBuild(conn, lockFactory)
   224  			err = scanBuild(build, untilRow, conn.EncryptionStrategy())
   225  			if err != nil {
   226  				return nil, Pagination{}, err
   227  			}
   228  
   229  			newPage.To = NewIntPtr(build.ID())
   230  		}
   231  		if !found {
   232  			return []Build{}, Pagination{}, nil
   233  		}
   234  	}
   235  
   236  	err = tx.Commit()
   237  	if err != nil {
   238  		return nil, Pagination{}, err
   239  	}
   240  
   241  	return getBuildsWithPagination(buildsQuery, minMaxIdQuery, newPage, conn, lockFactory)
   242  }
   243  
   244  func getBuildsWithPagination(buildsQuery, minMaxIdQuery sq.SelectBuilder, page Page, conn Conn, lockFactory lock.LockFactory) ([]Build, Pagination, error) {
   245  	var (
   246  		rows    *sql.Rows
   247  		err     error
   248  		reverse bool
   249  	)
   250  
   251  	origBuildsQuery := buildsQuery
   252  
   253  	tx, err := conn.Begin()
   254  	if err != nil {
   255  		return nil, Pagination{}, err
   256  	}
   257  
   258  	defer Rollback(tx)
   259  
   260  	buildsQuery = buildsQuery.Limit(uint64(page.Limit))
   261  
   262  	if page.From == nil && page.To == nil { // none
   263  		buildsQuery = buildsQuery.
   264  			OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC")
   265  	} else if page.From != nil && page.To == nil { // only from
   266  		buildsQuery = buildsQuery.
   267  			Where(sq.GtOrEq{"b.id": uint64(*page.From)}).
   268  			OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC")
   269  		reverse = true
   270  	} else if page.From == nil && page.To != nil { // only to
   271  		buildsQuery = buildsQuery.
   272  			Where(sq.LtOrEq{"b.id": uint64(*page.To)}).
   273  			OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC")
   274  	} else if page.From != nil && page.To != nil { // both
   275  		if *page.From > *page.To {
   276  			return nil, Pagination{}, fmt.Errorf("Invalid range boundaries")
   277  		}
   278  
   279  		buildsQuery = buildsQuery.Where(
   280  			sq.And{
   281  				sq.GtOrEq{"b.id": uint64(*page.From)},
   282  				sq.LtOrEq{"b.id": uint64(*page.To)},
   283  			}).
   284  			OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC")
   285  	}
   286  
   287  	rows, err = buildsQuery.RunWith(tx).Query()
   288  	if err != nil {
   289  		return nil, Pagination{}, err
   290  	}
   291  
   292  	defer Close(rows)
   293  
   294  	builds := make([]Build, 0)
   295  	for rows.Next() {
   296  		build := newEmptyBuild(conn, lockFactory)
   297  		err = scanBuild(build, rows, conn.EncryptionStrategy())
   298  		if err != nil {
   299  			return nil, Pagination{}, err
   300  		}
   301  
   302  		builds = append(builds, build)
   303  	}
   304  
   305  	if reverse {
   306  		for i, j := 0, len(builds)-1; i < j; i, j = i+1, j-1 {
   307  			builds[i], builds[j] = builds[j], builds[i]
   308  		}
   309  	}
   310  
   311  	if len(builds) == 0 {
   312  		return builds, Pagination{}, nil
   313  	}
   314  
   315  	newestBuild := builds[0]
   316  	oldestBuild := builds[len(builds)-1]
   317  
   318  	var pagination Pagination
   319  
   320  	row := origBuildsQuery.
   321  		Where(sq.Lt{"b.id": oldestBuild.ID()}).
   322  		OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC").
   323  		Limit(1).
   324  		RunWith(tx).
   325  		QueryRow()
   326  
   327  	build := newEmptyBuild(conn, lockFactory)
   328  	err = scanBuild(build, row, conn.EncryptionStrategy())
   329  	if err != nil && err != sql.ErrNoRows {
   330  		return builds, Pagination{}, err
   331  	} else if err == nil {
   332  		pagination.Older = &Page{
   333  			To:    &build.id,
   334  			Limit: page.Limit,
   335  		}
   336  	}
   337  
   338  	row = origBuildsQuery.
   339  		Where(sq.Gt{"b.id": newestBuild.ID()}).
   340  		OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC").
   341  		Limit(1).
   342  		RunWith(tx).
   343  		QueryRow()
   344  
   345  	build = newEmptyBuild(conn, lockFactory)
   346  	err = scanBuild(build, row, conn.EncryptionStrategy())
   347  	if err != nil && err != sql.ErrNoRows {
   348  		return builds, Pagination{}, err
   349  	} else if err == nil {
   350  		pagination.Newer = &Page{
   351  			From:  &build.id,
   352  			Limit: page.Limit,
   353  		}
   354  	}
   355  
   356  	err = tx.Commit()
   357  	if err != nil {
   358  		return nil, Pagination{}, err
   359  	}
   360  
   361  	return builds, pagination, nil
   362  }