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

     1  package db
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	sq "github.com/Masterminds/squirrel"
     8  	"github.com/pf-qiu/concourse/v6/atc"
     9  	"github.com/lib/pq"
    10  )
    11  
    12  //go:generate counterfeiter . ContainerRepository
    13  
    14  type ContainerRepository interface {
    15  	FindOrphanedContainers() ([]CreatingContainer, []CreatedContainer, []DestroyingContainer, error)
    16  	DestroyFailedContainers() (int, error)
    17  	FindDestroyingContainers(workerName string) ([]string, error)
    18  	RemoveDestroyingContainers(workerName string, currentHandles []string) (int, error)
    19  	UpdateContainersMissingSince(workerName string, handles []string) error
    20  	RemoveMissingContainers(time.Duration) (int, error)
    21  	DestroyUnknownContainers(workerName string, reportedHandles []string) (int, error)
    22  }
    23  
    24  type containerRepository struct {
    25  	conn Conn
    26  }
    27  
    28  func NewContainerRepository(conn Conn) ContainerRepository {
    29  	return &containerRepository{
    30  		conn: conn,
    31  	}
    32  }
    33  
    34  func diff(a, b []string) (diff []string) {
    35  	m := make(map[string]bool)
    36  
    37  	for _, item := range b {
    38  		m[item] = true
    39  	}
    40  
    41  	for _, item := range a {
    42  		if _, ok := m[item]; !ok {
    43  			diff = append(diff, item)
    44  		}
    45  	}
    46  
    47  	return
    48  }
    49  
    50  func (repository *containerRepository) queryContainerHandles(tx Tx, cond sq.Eq) ([]string, error) {
    51  	query, args, err := psql.Select("handle").From("containers").Where(cond).ToSql()
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	rows, err := tx.Query(query, args...)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	defer Close(rows)
    62  
    63  	handles := []string{}
    64  
    65  	for rows.Next() {
    66  		var handle = "handle"
    67  		columns := []interface{}{&handle}
    68  
    69  		err = rows.Scan(columns...)
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  		handles = append(handles, handle)
    74  	}
    75  
    76  	return handles, nil
    77  }
    78  
    79  func (repository *containerRepository) UpdateContainersMissingSince(workerName string, reportedHandles []string) error {
    80  	// clear out missing_since for reported containers
    81  	query, args, err := psql.Update("containers").
    82  		Set("missing_since", nil).
    83  		Where(
    84  			sq.And{
    85  				sq.NotEq{
    86  					"missing_since": nil,
    87  				},
    88  				sq.Eq{
    89  					"handle": reportedHandles,
    90  				},
    91  			},
    92  		).ToSql()
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	tx, err := repository.conn.Begin()
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	defer Rollback(tx)
   103  
   104  	rows, err := tx.Query(query, args...)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	Close(rows)
   110  
   111  	dbHandles, err := repository.queryContainerHandles(tx, sq.Eq{
   112  		"worker_name":   workerName,
   113  		"missing_since": nil,
   114  	})
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	handles := diff(dbHandles, reportedHandles)
   120  
   121  	query, args, err = psql.Update("containers").
   122  		Set("missing_since", sq.Expr("now()")).
   123  		Where(sq.And{
   124  			sq.Eq{"handle": handles},
   125  			sq.NotEq{"state": atc.ContainerStateCreating},
   126  		}).ToSql()
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	_, err = tx.Exec(query, args...)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	err = tx.Commit()
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  func (repository *containerRepository) FindDestroyingContainers(workerName string) ([]string, error) {
   145  	tx, err := repository.conn.Begin()
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	defer Rollback(tx)
   151  
   152  	destroyingContainers, err := repository.queryContainerHandles(
   153  		tx,
   154  		sq.Eq{
   155  			"state":       atc.ContainerStateDestroying,
   156  			"worker_name": workerName,
   157  		},
   158  	)
   159  
   160  	err = tx.Commit()
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	return destroyingContainers, err
   166  }
   167  
   168  func (repository *containerRepository) RemoveMissingContainers(gracePeriod time.Duration) (int, error) {
   169  	result, err := psql.Delete("containers c USING workers w").
   170  		Where(sq.Expr("c.worker_name = w.name")).
   171  		Where(
   172  			sq.And{
   173  				sq.Expr(fmt.Sprintf("c.state='%s'", atc.ContainerStateCreated)),
   174  				sq.Expr(fmt.Sprintf("w.state!='%s'", WorkerStateStalled)),
   175  				sq.Expr(fmt.Sprintf("NOW() - missing_since > '%s'", fmt.Sprintf("%.0f seconds", gracePeriod.Seconds()))),
   176  			},
   177  		).RunWith(repository.conn).
   178  		Exec()
   179  
   180  	if err != nil {
   181  		return 0, err
   182  	}
   183  
   184  	affected, err := result.RowsAffected()
   185  	if err != nil {
   186  		return 0, err
   187  	}
   188  
   189  	return int(affected), nil
   190  }
   191  
   192  func (repository *containerRepository) RemoveDestroyingContainers(workerName string, handlesToIgnore []string) (int, error) {
   193  	rows, err := psql.Delete("containers").
   194  		Where(
   195  			sq.And{
   196  				sq.Eq{
   197  					"worker_name": workerName,
   198  				},
   199  				sq.NotEq{
   200  					"handle": handlesToIgnore,
   201  				},
   202  				sq.Eq{
   203  					"state": atc.ContainerStateDestroying,
   204  				},
   205  			},
   206  		).RunWith(repository.conn).
   207  		Exec()
   208  
   209  	if err != nil {
   210  		return 0, err
   211  	}
   212  
   213  	affected, err := rows.RowsAffected()
   214  	if err != nil {
   215  		return 0, err
   216  	}
   217  
   218  	return int(affected), nil
   219  }
   220  
   221  func (repository *containerRepository) FindOrphanedContainers() ([]CreatingContainer, []CreatedContainer, []DestroyingContainer, error) {
   222  	query, args, err := selectContainers("c").
   223  		LeftJoin("builds b ON b.id = c.build_id").
   224  		LeftJoin("containers icc ON icc.id = c.image_check_container_id").
   225  		LeftJoin("containers igc ON igc.id = c.image_get_container_id").
   226  		Where(sq.Or{
   227  			sq.Eq{
   228  				"c.build_id":                         nil,
   229  				"c.image_check_container_id":         nil,
   230  				"c.image_get_container_id":           nil,
   231  				"c.resource_config_check_session_id": nil,
   232  			},
   233  			sq.And{
   234  				sq.NotEq{"c.build_id": nil},
   235  				sq.Eq{"b.interceptible": false},
   236  			},
   237  			sq.And{
   238  				sq.NotEq{"c.image_check_container_id": nil},
   239  				sq.NotEq{"icc.state": atc.ContainerStateCreating},
   240  			},
   241  			sq.And{
   242  				sq.NotEq{"c.image_get_container_id": nil},
   243  				sq.NotEq{"igc.state": atc.ContainerStateCreating},
   244  			},
   245  		}).
   246  		ToSql()
   247  	if err != nil {
   248  		return nil, nil, nil, err
   249  	}
   250  
   251  	rows, err := repository.conn.Query(query, args...)
   252  	if err != nil {
   253  		return nil, nil, nil, err
   254  	}
   255  
   256  	defer Close(rows)
   257  
   258  	creatingContainers := []CreatingContainer{}
   259  	createdContainers := []CreatedContainer{}
   260  	destroyingContainers := []DestroyingContainer{}
   261  
   262  	var (
   263  		creatingContainer   CreatingContainer
   264  		createdContainer    CreatedContainer
   265  		destroyingContainer DestroyingContainer
   266  	)
   267  
   268  	for rows.Next() {
   269  		creatingContainer, createdContainer, destroyingContainer, _, err = scanContainer(rows, repository.conn)
   270  		if err != nil {
   271  			return nil, nil, nil, err
   272  		}
   273  
   274  		if creatingContainer != nil {
   275  			creatingContainers = append(creatingContainers, creatingContainer)
   276  		}
   277  
   278  		if createdContainer != nil {
   279  			createdContainers = append(createdContainers, createdContainer)
   280  		}
   281  
   282  		if destroyingContainer != nil {
   283  			destroyingContainers = append(destroyingContainers, destroyingContainer)
   284  		}
   285  	}
   286  
   287  	err = rows.Err()
   288  	if err != nil {
   289  		return nil, nil, nil, err
   290  	}
   291  
   292  	return creatingContainers, createdContainers, destroyingContainers, nil
   293  }
   294  
   295  func selectContainers(asOptional ...string) sq.SelectBuilder {
   296  	columns := []string{"id", "handle", "worker_name", "last_hijack", "state"}
   297  	columns = append(columns, containerMetadataColumns...)
   298  
   299  	table := "containers"
   300  	if len(asOptional) > 0 {
   301  		as := asOptional[0]
   302  		for i, c := range columns {
   303  			columns[i] = as + "." + c
   304  		}
   305  
   306  		table += " " + as
   307  	}
   308  
   309  	return psql.Select(columns...).From(table)
   310  }
   311  
   312  func scanContainer(row sq.RowScanner, conn Conn) (CreatingContainer, CreatedContainer, DestroyingContainer, FailedContainer, error) {
   313  	var (
   314  		id         int
   315  		handle     string
   316  		workerName string
   317  		lastHijack pq.NullTime
   318  		state      string
   319  
   320  		metadata ContainerMetadata
   321  	)
   322  
   323  	columns := []interface{}{&id, &handle, &workerName, &lastHijack, &state}
   324  	columns = append(columns, metadata.ScanTargets()...)
   325  
   326  	err := row.Scan(columns...)
   327  	if err != nil {
   328  		return nil, nil, nil, nil, err
   329  	}
   330  
   331  	switch state {
   332  	case atc.ContainerStateCreating:
   333  		return newCreatingContainer(
   334  			id,
   335  			handle,
   336  			workerName,
   337  			metadata,
   338  			conn,
   339  		), nil, nil, nil, nil
   340  	case atc.ContainerStateCreated:
   341  		return nil, newCreatedContainer(
   342  			id,
   343  			handle,
   344  			workerName,
   345  			metadata,
   346  			lastHijack.Time,
   347  			conn,
   348  		), nil, nil, nil
   349  	case atc.ContainerStateDestroying:
   350  		return nil, nil, newDestroyingContainer(
   351  			id,
   352  			handle,
   353  			workerName,
   354  			metadata,
   355  			conn,
   356  		), nil, nil
   357  	case atc.ContainerStateFailed:
   358  		return nil, nil, nil, newFailedContainer(
   359  			id,
   360  			handle,
   361  			workerName,
   362  			metadata,
   363  			conn,
   364  		), nil
   365  	}
   366  
   367  	return nil, nil, nil, nil, nil
   368  }
   369  
   370  func (repository *containerRepository) DestroyFailedContainers() (int, error) {
   371  	result, err := psql.Update("containers").
   372  		Set("state", atc.ContainerStateDestroying).
   373  		Where(sq.Eq{"state": string(atc.ContainerStateFailed)}).
   374  		RunWith(repository.conn).
   375  		Exec()
   376  
   377  	if err != nil {
   378  		return 0, err
   379  	}
   380  
   381  	affected, err := result.RowsAffected()
   382  	if err != nil {
   383  		return 0, err
   384  	}
   385  
   386  	return int(affected), nil
   387  }
   388  
   389  func (repository *containerRepository) DestroyUnknownContainers(workerName string, reportedHandles []string) (int, error) {
   390  	tx, err := repository.conn.Begin()
   391  	if err != nil {
   392  		return 0, err
   393  	}
   394  
   395  	defer Rollback(tx)
   396  
   397  	dbHandles, err := repository.queryContainerHandles(tx, sq.Eq{
   398  		"worker_name": workerName,
   399  	})
   400  	if err != nil {
   401  		return 0, err
   402  	}
   403  
   404  	unknownHandles := diff(reportedHandles, dbHandles)
   405  
   406  	if len(unknownHandles) == 0 {
   407  		return 0, nil
   408  	}
   409  
   410  	insertBuilder := psql.Insert("containers").Columns(
   411  		"handle",
   412  		"worker_name",
   413  		"state",
   414  	)
   415  	for _, unknownHandle := range unknownHandles {
   416  		insertBuilder = insertBuilder.Values(
   417  			unknownHandle,
   418  			workerName,
   419  			atc.ContainerStateDestroying,
   420  		)
   421  	}
   422  	_, err = insertBuilder.RunWith(tx).Exec()
   423  	if err != nil {
   424  		return 0, err
   425  	}
   426  
   427  	err = tx.Commit()
   428  	if err != nil {
   429  		return 0, err
   430  	}
   431  
   432  	return len(unknownHandles), nil
   433  }