github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/topgun/common/common.go (about)

     1  package common
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"database/sql"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"regexp"
    14  	"strings"
    15  	"time"
    16  
    17  	"sigs.k8s.io/yaml"
    18  
    19  	_ "github.com/lib/pq"
    20  
    21  	gclient "code.cloudfoundry.org/garden/client"
    22  	gconn "code.cloudfoundry.org/garden/client/connection"
    23  	"code.cloudfoundry.org/lager"
    24  	"code.cloudfoundry.org/lager/lagertest"
    25  	sq "github.com/Masterminds/squirrel"
    26  	bclient "github.com/concourse/baggageclaim/client"
    27  	"golang.org/x/oauth2"
    28  
    29  	"github.com/pf-qiu/concourse/v6/go-concourse/concourse"
    30  	. "github.com/pf-qiu/concourse/v6/topgun"
    31  	. "github.com/onsi/ginkgo"
    32  	. "github.com/onsi/gomega"
    33  	"github.com/onsi/gomega/gexec"
    34  )
    35  
    36  var (
    37  	deploymentNamePrefix string
    38  	suiteName            string
    39  
    40  	Fly                       = FlyCli{}
    41  	DeploymentName, flyTarget string
    42  	instances                 map[string][]BoshInstance
    43  	jobInstances              map[string][]BoshInstance
    44  
    45  	dbInstance *BoshInstance
    46  	DbConn     *sql.DB
    47  
    48  	webInstance    *BoshInstance
    49  	AtcExternalURL string
    50  	AtcUsername    string
    51  	AtcPassword    string
    52  
    53  	WorkerGardenClient       gclient.Client
    54  	WorkerBaggageclaimClient bclient.Client
    55  
    56  	concourseReleaseVersion, bpmReleaseVersion, postgresReleaseVersion string
    57  	vaultReleaseVersion, credhubReleaseVersion, uaaReleaseVersion      string
    58  	stemcellVersion                                                    string
    59  	backupAndRestoreReleaseVersion                                     string
    60  
    61  	pipelineName string
    62  
    63  	Logger *lagertest.TestLogger
    64  
    65  	tmp string
    66  )
    67  
    68  var _ = BeforeEach(func() {
    69  	SetDefaultEventuallyTimeout(2 * time.Minute)
    70  	SetDefaultEventuallyPollingInterval(time.Second)
    71  	SetDefaultConsistentlyDuration(time.Minute)
    72  	SetDefaultConsistentlyPollingInterval(time.Second)
    73  
    74  	Logger = lagertest.NewTestLogger("test")
    75  
    76  	deploymentNamePrefix = os.Getenv("DEPLOYMENT_NAME_PREFIX")
    77  	if deploymentNamePrefix == "" {
    78  		deploymentNamePrefix = "concourse-topgun"
    79  	}
    80  
    81  	suiteName = os.Getenv("SUITE")
    82  	if suiteName != "" {
    83  		deploymentNamePrefix += "-" + suiteName
    84  	}
    85  
    86  	concourseReleaseVersion = os.Getenv("CONCOURSE_RELEASE_VERSION")
    87  	if concourseReleaseVersion == "" {
    88  		concourseReleaseVersion = "latest"
    89  	}
    90  
    91  	bpmReleaseVersion = os.Getenv("BPM_RELEASE_VERSION")
    92  	if bpmReleaseVersion == "" {
    93  		bpmReleaseVersion = "latest"
    94  	}
    95  
    96  	postgresReleaseVersion = os.Getenv("POSTGRES_RELEASE_VERSION")
    97  	if postgresReleaseVersion == "" {
    98  		postgresReleaseVersion = "latest"
    99  	}
   100  
   101  	vaultReleaseVersion = os.Getenv("VAULT_RELEASE_VERSION")
   102  	if vaultReleaseVersion == "" {
   103  		vaultReleaseVersion = "latest"
   104  	}
   105  
   106  	credhubReleaseVersion = os.Getenv("CREDHUB_RELEASE_VERSION")
   107  	if credhubReleaseVersion == "" {
   108  		credhubReleaseVersion = "latest"
   109  	}
   110  
   111  	uaaReleaseVersion = os.Getenv("UAA_RELEASE_VERSION")
   112  	if uaaReleaseVersion == "" {
   113  		uaaReleaseVersion = "latest"
   114  	}
   115  
   116  	stemcellVersion = os.Getenv("STEMCELL_VERSION")
   117  	if stemcellVersion == "" {
   118  		stemcellVersion = "latest"
   119  	}
   120  	backupAndRestoreReleaseVersion = os.Getenv("BACKUP_AND_RESTORE_SDK_RELEASE_VERSION")
   121  	if backupAndRestoreReleaseVersion == "" {
   122  		backupAndRestoreReleaseVersion = "latest"
   123  	}
   124  
   125  	deploymentNumber := GinkgoParallelNode()
   126  
   127  	DeploymentName = fmt.Sprintf("%s-%d", deploymentNamePrefix, deploymentNumber)
   128  	Fly.Target = DeploymentName
   129  
   130  	var err error
   131  	tmp, err = ioutil.TempDir("", "topgun-tmp")
   132  	Expect(err).ToNot(HaveOccurred())
   133  
   134  	Fly.Home = filepath.Join(tmp, "fly-home")
   135  	err = os.Mkdir(Fly.Home, 0755)
   136  	Expect(err).ToNot(HaveOccurred())
   137  
   138  	WaitForDeploymentAndCompileLocks()
   139  	Bosh("delete-deployment", "--force")
   140  
   141  	instances = map[string][]BoshInstance{}
   142  	jobInstances = map[string][]BoshInstance{}
   143  
   144  	dbInstance = nil
   145  	DbConn = nil
   146  	webInstance = nil
   147  	AtcExternalURL = ""
   148  	AtcUsername = "test"
   149  	AtcPassword = "test"
   150  })
   151  
   152  var _ = AfterEach(func() {
   153  	test := CurrentGinkgoTestDescription()
   154  	if test.Failed {
   155  		dir := filepath.Join("logs", fmt.Sprintf("%s.%d", filepath.Base(test.FileName), test.LineNumber))
   156  
   157  		err := os.MkdirAll(dir, 0755)
   158  		Expect(err).ToNot(HaveOccurred())
   159  
   160  		TimestampedBy("saving logs to " + dir + " due to test failure")
   161  		Bosh("logs", "--dir", dir)
   162  	}
   163  
   164  	DeleteAllContainers()
   165  
   166  	WaitForDeploymentAndCompileLocks()
   167  	Bosh("delete-deployment")
   168  
   169  	Expect(os.RemoveAll(tmp)).To(Succeed())
   170  })
   171  
   172  type BoshInstance struct {
   173  	Name  string
   174  	Group string
   175  	ID    string
   176  	IP    string
   177  	DNS   string
   178  }
   179  
   180  func StartDeploy(manifest string, args ...string) *gexec.Session {
   181  	WaitForDeploymentAndCompileLocks()
   182  
   183  	var modifiedSuiteName string
   184  	if suiteName != "" {
   185  		modifiedSuiteName = "-" + suiteName
   186  	}
   187  
   188  	return SpawnBosh(
   189  		append([]string{
   190  			"deploy", manifest,
   191  			"--vars-store", filepath.Join(tmp, DeploymentName+"-vars.yml"),
   192  			"-v", "suite='" + modifiedSuiteName + "'",
   193  			"-v", "deployment_name='" + DeploymentName + "'",
   194  			"-v", "concourse_release_version='" + concourseReleaseVersion + "'",
   195  			"-v", "bpm_release_version='" + bpmReleaseVersion + "'",
   196  			"-v", "postgres_release_version='" + postgresReleaseVersion + "'",
   197  			"-v", "vault_release_version='" + vaultReleaseVersion + "'",
   198  			"-v", "credhub_release_version='" + credhubReleaseVersion + "'",
   199  			"-v", "uaa_release_version='" + uaaReleaseVersion + "'",
   200  			"-v", "backup_and_restore_sdk_release_version='" + backupAndRestoreReleaseVersion + "'",
   201  			"-v", "stemcell_version='" + stemcellVersion + "'",
   202  		}, args...)...,
   203  	)
   204  }
   205  
   206  func Deploy(manifest string, args ...string) {
   207  	if DbConn != nil {
   208  		Expect(DbConn.Close()).To(Succeed())
   209  	}
   210  
   211  	for {
   212  		deploy := StartDeploy(manifest, args...)
   213  		<-deploy.Exited
   214  		if deploy.ExitCode() != 0 {
   215  			if strings.Contains(string(deploy.Out.Contents()), "Timed out pinging") {
   216  				fmt.Fprintln(GinkgoWriter, "detected ping timeout; trying again...")
   217  				continue
   218  			}
   219  
   220  			Fail("deploy failed")
   221  		}
   222  
   223  		break
   224  	}
   225  
   226  	instances, jobInstances = LoadJobInstances()
   227  
   228  	webInstance = JobInstance("web")
   229  	if webInstance != nil {
   230  		AtcExternalURL = fmt.Sprintf("http://%s:8080", webInstance.IP)
   231  		Fly.Login(AtcUsername, AtcPassword, AtcExternalURL)
   232  
   233  		WaitForWorkersToBeRunning(len(JobInstances("worker")) + len(JobInstances("other_worker")))
   234  
   235  		workers := FlyTable("workers", "-d")
   236  		if len(workers) > 0 {
   237  			worker := workers[0]
   238  			WorkerGardenClient = gclient.New(gconn.New("tcp", worker["garden address"]))
   239  			WorkerBaggageclaimClient = bclient.NewWithHTTPClient(worker["baggageclaim url"], http.DefaultClient)
   240  		} else {
   241  			WorkerGardenClient = nil
   242  			WorkerBaggageclaimClient = nil
   243  		}
   244  	}
   245  
   246  	dbInstance = JobInstance("postgres")
   247  
   248  	if dbInstance != nil {
   249  		var err error
   250  		DbConn, err = sql.Open("postgres", fmt.Sprintf("postgres://atc:dummy-password@%s:5432/atc?sslmode=disable", dbInstance.IP))
   251  		Expect(err).ToNot(HaveOccurred())
   252  	}
   253  }
   254  
   255  func Instance(name string) *BoshInstance {
   256  	is := instances[name]
   257  	if len(is) == 0 {
   258  		return nil
   259  	}
   260  
   261  	return &is[0]
   262  }
   263  
   264  func JobInstance(job string) *BoshInstance {
   265  	is := jobInstances[job]
   266  	if len(is) == 0 {
   267  		return nil
   268  	}
   269  
   270  	return &is[0]
   271  }
   272  
   273  func JobInstances(job string) []BoshInstance {
   274  	return jobInstances[job]
   275  }
   276  
   277  func LoadJobInstances() (map[string][]BoshInstance, map[string][]BoshInstance) {
   278  	session := SpawnBosh("instances", "-p", "--dns")
   279  	<-session.Exited
   280  	Expect(session.ExitCode()).To(Equal(0))
   281  
   282  	output := string(session.Out.Contents())
   283  
   284  	instances := map[string][]BoshInstance{}
   285  	jobInstances := map[string][]BoshInstance{}
   286  
   287  	lines := strings.Split(output, "\n")
   288  	var instance BoshInstance
   289  	for _, line := range lines {
   290  		instanceMatch := InstanceRow.FindStringSubmatch(line)
   291  		if len(instanceMatch) > 0 {
   292  			group := instanceMatch[1]
   293  			id := instanceMatch[2]
   294  
   295  			instance = BoshInstance{
   296  				Name:  group + "/" + id,
   297  				Group: group,
   298  				ID:    id,
   299  				IP:    instanceMatch[4],
   300  				DNS:   instanceMatch[5],
   301  			}
   302  
   303  			instances[group] = append(instances[group], instance)
   304  
   305  			continue
   306  		}
   307  
   308  		jobMatch := JobRow.FindStringSubmatch(line)
   309  		if len(jobMatch) > 0 {
   310  			jobName := jobMatch[2]
   311  			jobInstances[jobName] = append(jobInstances[jobName], instance)
   312  		}
   313  	}
   314  
   315  	return instances, jobInstances
   316  }
   317  
   318  func Bosh(argv ...string) *gexec.Session {
   319  	session := SpawnBosh(argv...)
   320  	Wait(session)
   321  	return session
   322  }
   323  
   324  func SpawnBosh(argv ...string) *gexec.Session {
   325  	return Start(nil, "bosh", append([]string{"-n", "-d", DeploymentName}, argv...)...)
   326  }
   327  
   328  func ConcourseClient() concourse.Client {
   329  	token, err := FetchToken(AtcExternalURL, AtcUsername, AtcPassword)
   330  	Expect(err).NotTo(HaveOccurred())
   331  
   332  	httpClient := &http.Client{
   333  		Transport: &oauth2.Transport{
   334  			Source: oauth2.StaticTokenSource(token),
   335  			Base: &http.Transport{
   336  				TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   337  			},
   338  		},
   339  	}
   340  
   341  	return concourse.NewClient(AtcExternalURL, httpClient, false)
   342  }
   343  
   344  func DeleteAllContainers() {
   345  	client := ConcourseClient()
   346  	workers, err := client.ListWorkers()
   347  	Expect(err).NotTo(HaveOccurred())
   348  
   349  	mainTeam := client.Team("main")
   350  	containers, err := mainTeam.ListContainers(map[string]string{})
   351  	Expect(err).NotTo(HaveOccurred())
   352  
   353  	for _, worker := range workers {
   354  		if worker.GardenAddr == "" {
   355  			continue
   356  		}
   357  
   358  		connection := gconn.New("tcp", worker.GardenAddr)
   359  		gardenClient := gclient.New(connection)
   360  		for _, container := range containers {
   361  			if container.WorkerName == worker.Name {
   362  				err = gardenClient.Destroy(container.ID)
   363  				if err != nil {
   364  					Logger.Error("failed-to-delete-container", err, lager.Data{"handle": container.ID})
   365  				}
   366  			}
   367  		}
   368  	}
   369  }
   370  
   371  func WaitForLandedWorker() string {
   372  	return WaitForWorkerInState("landed")
   373  }
   374  
   375  func WaitForRunningWorker() string {
   376  	return WaitForWorkerInState("running")
   377  }
   378  
   379  func WaitForStalledWorker() string {
   380  	return WaitForWorkerInState("stalled")
   381  }
   382  
   383  func WorkerState(name string) string {
   384  	workers := FlyTable("workers")
   385  
   386  	for _, w := range workers {
   387  		if w["name"] == name {
   388  			return w["state"]
   389  		}
   390  	}
   391  
   392  	return ""
   393  }
   394  
   395  func WaitForWorkerInState(desiredStates ...string) string {
   396  	var workerName string
   397  
   398  	Eventually(func() string {
   399  		workers := FlyTable("workers")
   400  
   401  		for _, worker := range workers {
   402  			name := worker["name"]
   403  			state := worker["state"]
   404  
   405  			anyMatched := false
   406  			for _, desiredState := range desiredStates {
   407  				if state == desiredState {
   408  					anyMatched = true
   409  				}
   410  			}
   411  
   412  			if !anyMatched {
   413  				continue
   414  			}
   415  
   416  			if workerName != "" {
   417  				Fail("multiple workers in states: " + strings.Join(desiredStates, ", "))
   418  			}
   419  
   420  			workerName = name
   421  		}
   422  
   423  		return workerName
   424  	}, 2*time.Minute, 5*time.Second).ShouldNot(BeEmpty(), "should have seen a worker in states: "+strings.Join(desiredStates, ", "))
   425  
   426  	return workerName
   427  }
   428  
   429  func FlyTable(argv ...string) []map[string]string {
   430  	session := Fly.Start(append([]string{"--print-table-headers"}, argv...)...)
   431  	<-session.Exited
   432  	Expect(session.ExitCode()).To(Equal(0))
   433  
   434  	result := []map[string]string{}
   435  
   436  	var headers []string
   437  	for i, cols := range ParseTable(string(session.Out.Contents())) {
   438  		if i == 0 {
   439  			headers = cols
   440  			continue
   441  		}
   442  
   443  		result = append(result, map[string]string{})
   444  
   445  		for j, header := range headers {
   446  			if header == "" || cols[j] == "" {
   447  				continue
   448  			}
   449  
   450  			result[i-1][header] = cols[j]
   451  		}
   452  	}
   453  
   454  	return result
   455  }
   456  
   457  func ParseTable(content string) [][]string {
   458  	result := [][]string{}
   459  
   460  	var expectedColumns int
   461  	rows := strings.Split(content, "\n")
   462  	for i, row := range rows {
   463  		if row == "" {
   464  			continue
   465  		}
   466  
   467  		columns := SplitTableColumns(row)
   468  		if i == 0 {
   469  			expectedColumns = len(columns)
   470  		} else {
   471  			Expect(columns).To(HaveLen(expectedColumns))
   472  		}
   473  
   474  		result = append(result, columns)
   475  	}
   476  
   477  	return result
   478  }
   479  
   480  func SplitTableColumns(row string) []string {
   481  	return regexp.MustCompile(`(\s{2,}|\t)`).Split(strings.TrimSpace(row), -1)
   482  }
   483  
   484  func WaitForWorkersToBeRunning(expected int) {
   485  	Eventually(func() interface{} {
   486  		workers := FlyTable("workers")
   487  
   488  		runningWorkers := []map[string]string{}
   489  		for _, worker := range workers {
   490  			if worker["state"] == "running" {
   491  				runningWorkers = append(runningWorkers, worker)
   492  			}
   493  		}
   494  
   495  		return runningWorkers
   496  	}).Should(HaveLen(expected), "expected all workers to be running")
   497  }
   498  
   499  func WorkersWithContainers() []string {
   500  	mainTeam := ConcourseClient().Team("main")
   501  	containers, err := mainTeam.ListContainers(map[string]string{})
   502  	Expect(err).NotTo(HaveOccurred())
   503  
   504  	usedWorkers := map[string]struct{}{}
   505  
   506  	for _, container := range containers {
   507  		usedWorkers[container.WorkerName] = struct{}{}
   508  	}
   509  
   510  	var workerNames []string
   511  	for worker := range usedWorkers {
   512  		workerNames = append(workerNames, worker)
   513  	}
   514  
   515  	return workerNames
   516  }
   517  
   518  func ContainersBy(condition, value string) []string {
   519  	containers := FlyTable("containers")
   520  
   521  	var handles []string
   522  	for _, c := range containers {
   523  		if c[condition] == value {
   524  			handles = append(handles, c["handle"])
   525  		}
   526  	}
   527  
   528  	return handles
   529  }
   530  
   531  func VolumesByResourceType(name string) []string {
   532  	volumes := FlyTable("volumes", "-d")
   533  
   534  	var handles []string
   535  	for _, v := range volumes {
   536  		if v["type"] == "resource" && strings.HasPrefix(v["identifier"], "name:"+name) {
   537  			handles = append(handles, v["handle"])
   538  		}
   539  	}
   540  
   541  	return handles
   542  }
   543  
   544  func WaitForDeploymentAndCompileLocks() {
   545  	cloudConfig := Start(nil, "bosh", "cloud-config")
   546  	<-cloudConfig.Exited
   547  	cc := struct {
   548  		Compilation struct {
   549  			Workers int
   550  		}
   551  	}{}
   552  	yaml.Unmarshal(cloudConfig.Out.Contents(), &cc)
   553  	numCompilationVms := cc.Compilation.Workers
   554  	for {
   555  		locks := Bosh("locks", "--column", "type", "--column", "resource", "--column", "task id")
   556  		isDeploymentLockClaimed := false
   557  		numCompileLocksClaimed := 0
   558  
   559  		for _, lock := range ParseTable(string(locks.Out.Contents())) {
   560  			if lock[0] == "deployment" && lock[1] == DeploymentName {
   561  				fmt.Fprintf(GinkgoWriter, "waiting for deployment lock (task id %s)...\n", lock[2])
   562  				isDeploymentLockClaimed = true
   563  			} else if lock[0] == "compile" {
   564  				numCompileLocksClaimed += 1
   565  			}
   566  		}
   567  
   568  		if isDeploymentLockClaimed || numCompileLocksClaimed >= numCompilationVms {
   569  			time.Sleep(5 * time.Second)
   570  		} else {
   571  			break
   572  		}
   573  	}
   574  }
   575  
   576  func PgDump() *gexec.Session {
   577  	dump := exec.Command("pg_dump", "-U", "atc", "-h", dbInstance.IP, "atc")
   578  	dump.Env = append(os.Environ(), "PGPASSWORD=dummy-password")
   579  	dump.Stdin = bytes.NewBufferString("dummy-password\n")
   580  	session, err := gexec.Start(dump, nil, GinkgoWriter)
   581  	Expect(err).ToNot(HaveOccurred())
   582  	<-session.Exited
   583  	Expect(session.ExitCode()).To(Equal(0))
   584  	return session
   585  }
   586  
   587  var Psql = sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
   588  
   589  var _ = SynchronizedBeforeSuite(func() []byte {
   590  	return []byte(BuildBinary())
   591  }, func(data []byte) {
   592  	Fly.Bin = string(data)
   593  })
   594  
   595  var _ = SynchronizedAfterSuite(func() {
   596  }, func() {
   597  	gexec.CleanupBuildArtifacts()
   598  })
   599  
   600  var InstanceRow = regexp.MustCompile(`^([^/]+)/([^\s]+)\s+-\s+(\w+)\s+z1\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+([^\s]+)\s*`)
   601  var JobRow = regexp.MustCompile(`^([^\s]+)\s+(\w+)\s+(\w+)\s+-\s+-\s+-\s*`)