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*`)