diff --git a/.github/workflows/integrate-cluster-cmd.yaml b/.github/workflows/integrate-cluster-cmd.yaml index e084284042..2bc3c54565 100644 --- a/.github/workflows/integrate-cluster-cmd.yaml +++ b/.github/workflows/integrate-cluster-cmd.yaml @@ -90,6 +90,8 @@ jobs: # if: always() run: | docker exec tiup-cluster-control bash /tiup-cluster/tests/tiup-cluster/script/pull_log.sh /tiup-cluster/logs + mkdir -p ./logs + docker cp tiup-cluster-control:/tiup-cluster/logs/. ./logs/ - name: Detect error log if: ${{ failure() }} @@ -103,7 +105,7 @@ jobs: uses: actions/upload-artifact@v6 with: overwrite: true - name: component_logs + name: component_logs-${{ matrix.cases }} path: ./logs - name: Output cluster debug log diff --git a/.github/workflows/integrate-cluster-scale.yaml b/.github/workflows/integrate-cluster-scale.yaml index eb07f7ed2d..518580a378 100644 --- a/.github/workflows/integrate-cluster-scale.yaml +++ b/.github/workflows/integrate-cluster-scale.yaml @@ -91,6 +91,8 @@ jobs: # if: always() run: | docker exec tiup-cluster-control bash /tiup-cluster/tests/tiup-cluster/script/pull_log.sh /tiup-cluster/logs + mkdir -p ./logs + docker cp tiup-cluster-control:/tiup-cluster/logs/. ./logs/ - name: Detect error log if: ${{ failure() }} @@ -103,7 +105,7 @@ jobs: uses: actions/upload-artifact@v6 with: overwrite: true - name: cluster_logs + name: cluster_logs-${{ matrix.cases }} path: ./logs - name: Output cluster debug log diff --git a/CHANGELOG.md b/CHANGELOG.md index 2446f0505f..451958b075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ TiUP Changelog +## [1.17.1] 2026-09-08 + +### Fixes + +- `tiup-cluster` fix Grafana dashboard datasource replacement when using VictoriaMetrics (#2732, @Defined2014) +- `tiup-cluster` collect node_exporter metrics on dedicated TiDB Dashboard hosts (#2734, @mayjiang0203) +- `tiup-cluster` preserve shared exporter directories and systemd units when destroying a cluster with `ignore_exporter: true` (#2737, @Smityz, @ekexium) +- `tiup-cluster` prevent concurrent TLS certificate distribution from copying an empty CA certificate and blocking component startup (#2738, @ekexium) + ## [1.17.0] 2026-07-27 ### New Features diff --git a/docker/node/Dockerfile b/docker/node/Dockerfile index b96ef1d47e..bf898971ce 100644 --- a/docker/node/Dockerfile +++ b/docker/node/Dockerfile @@ -1,19 +1,17 @@ # Based on the deprecated `https://github.com/tutumcloud/tutum-debian` -FROM golang:1.24-bullseye +FROM golang:1.24-bookworm # Use mirrors for poor network... #RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \ # sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list # Install packages -# JRE 11 is installed for tispark testing, a tispark node could be started -# with Java 11, but is not going to work properly. However, we don't test -# for SQL in CI, so it's ok to use Java 11 instead of Java 8. RUN apt-get update && \ apt-get -y install \ dos2unix \ openssh-server \ - openjdk-11-jre-headless \ + systemd systemd-sysv \ + psmisc \ sudo vim \ iproute2 \ && \ diff --git a/pkg/cluster/operation/destroy.go b/pkg/cluster/operation/destroy.go index 0f541f387e..76c1c8f69b 100644 --- a/pkg/cluster/operation/destroy.go +++ b/pkg/cluster/operation/destroy.go @@ -58,7 +58,7 @@ func Destroy( for _, inst := range insts { instCount[inst.GetManageHost()]-- if instCount[inst.GetManageHost()] == 0 { - if cluster.GetMonitoredOptions() != nil { + if cluster.GetMonitoredOptions() != nil && !inst.IgnoreMonitorAgent() { if err := DestroyMonitored(ctx, inst, cluster.GetMonitoredOptions(), options.OptTimeout, cluster.BaseTopo().GlobalOptions.SystemdMode); err != nil && !options.Force { return err } diff --git a/pkg/cluster/operation/destroy_test.go b/pkg/cluster/operation/destroy_test.go new file mode 100644 index 0000000000..3bcb9b3f63 --- /dev/null +++ b/pkg/cluster/operation/destroy_test.go @@ -0,0 +1,128 @@ +// Copyright 2026 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package operator + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/pingcap/tiup/pkg/cluster/ctxt" + "github.com/pingcap/tiup/pkg/cluster/spec" + logprinter "github.com/pingcap/tiup/pkg/logger/printer" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +type destroyExecutor struct { + commands []string +} + +func (e *destroyExecutor) Execute(_ context.Context, cmd string, _ bool, _ ...time.Duration) ([]byte, []byte, error) { + e.commands = append(e.commands, cmd) + // Empty ss output models stopped ports, so an unexpected exporter cleanup + // is caught by the assertions without waiting for a timeout. + return nil, nil, nil +} + +func (*destroyExecutor) Transfer(_ context.Context, _, _ string, _ bool, _ int, _ bool) error { + return nil +} + +func TestDestroyIgnoreExporter(t *testing.T) { + for _, tc := range []struct { + name string + ignoreExporter bool + multipleInstances bool + force bool + }{ + {name: "shared", ignoreExporter: true}, + {name: "shared_multiple_instances", ignoreExporter: true, multipleInstances: true}, + {name: "shared_force", ignoreExporter: true, force: true}, + {name: "managed"}, + {name: "managed_multiple_instances", multipleInstances: true}, + } { + t.Run(tc.name, func(t *testing.T) { + topology := fmt.Sprintf(` +global: + user: tidb + deploy_dir: /tidb-deploy + data_dir: /tidb-data +monitored: + deploy_dir: /tidb-deploy/monitor-9100 + data_dir: /tidb-data/monitor-9100 + log_dir: /tidb-log/monitor-9100 +pd_servers: + - host: 192.0.2.1 + ignore_exporter: %t +`, tc.ignoreExporter) + if tc.multipleInstances { + topology += fmt.Sprintf(` +tidb_servers: + - host: 192.0.2.1 + ignore_exporter: %t +`, tc.ignoreExporter) + } + var topo spec.Specification + require.NoError(t, yaml.Unmarshal([]byte(topology), &topo)) + + exec := &destroyExecutor{} + ctx := ctxt.New(context.Background(), 1, logprinter.NewLogger("")) + inner := ctxt.GetInner(ctx) + inner.SetExecutor("192.0.2.1", exec) + inner.PublicKeyPath = filepath.Join(t.TempDir(), "id_rsa.pub") + require.NoError(t, os.WriteFile(inner.PublicKeyPath, []byte("ssh-ed25519 test-key"), 0600)) + + require.NoError(t, Destroy(ctx, &topo, Options{Force: tc.force})) + + // Destroy must still remove the cluster's own components. + require.Contains(t, exec.commands, "rm -rf /etc/systemd/system/pd-2379.service;") + if tc.multipleInstances { + require.Contains(t, exec.commands, "rm -rf /etc/systemd/system/tidb-4000.service;") + } + + wantDeletes := 1 + if tc.ignoreExporter { + wantDeletes = 0 + } + for _, path := range []string{ + "/tidb-deploy/monitor-9100", + "/tidb-data/monitor-9100", + "/tidb-log/monitor-9100", + "/etc/systemd/system/node_exporter-9100.service", + "/etc/systemd/system/blackbox_exporter-9115.service", + } { + deletes := 0 + for _, cmd := range exec.commands { + if strings.HasPrefix(cmd, "rm -rf ") && strings.Contains(cmd, path) { + deletes++ + } + } + require.Equal(t, wantDeletes, deletes, "deletions of %s: %v", path, exec.commands) + } + + portChecks := 0 + for _, cmd := range exec.commands { + if cmd == "ss -ltn" { + portChecks++ + } + } + require.Equal(t, 2*wantDeletes, portChecks, "each managed exporter port is checked once") + }) + } +} diff --git a/pkg/cluster/spec/grafana.go b/pkg/cluster/spec/grafana.go index 13ddaed8ab..97b037dce7 100644 --- a/pkg/cluster/spec/grafana.go +++ b/pkg/cluster/spec/grafana.go @@ -374,13 +374,16 @@ func (i *GrafanaInstance) initDashboards(ctx context.Context, e ctxt.Executor, s } // Deal with the cluster name and datasource + // Replace literal dashboard placeholders first. The datasource name can contain + // "test-cluster" (for example, "test-cluster-vm"), so replacing these literals + // after datasource placeholders would rewrite the generated name a second time. for _, cmd := range []string{ + `find %s -type f -exec sed -i 's/test-cluster/%s/g' {} \;`, + `find %s -type f -exec sed -i 's/Test-Cluster/%s/g' {} \;`, `find %s -type f -exec sed -i 's/\${DS_.*-CLUSTER}/%s/g' {} \;`, `find %s -type f -exec sed -i 's/DS_.*-CLUSTER/%s/g' {} \;`, `find %s -type f -exec sed -i 's/\${DS_LIGHTNING}/%s/g' {} \;`, `find %s -type f -exec sed -i 's/DS_LIGHTNING/%s/g' {} \;`, - `find %s -type f -exec sed -i 's/test-cluster/%s/g' {} \;`, - `find %s -type f -exec sed -i 's/Test-Cluster/%s/g' {} \;`, } { cmd := fmt.Sprintf(cmd, dashboardsDir, datasourceName) _, stderr, err := e.Execute(ctx, cmd, false) diff --git a/pkg/cluster/spec/grafana_test.go b/pkg/cluster/spec/grafana_test.go index 901f7cefbd..1de58af724 100644 --- a/pkg/cluster/spec/grafana_test.go +++ b/pkg/cluster/spec/grafana_test.go @@ -282,6 +282,18 @@ func TestVictoriaMetricsDefaultDatasource(t *testing.T) { err = os.MkdirAll(binDir, 0755) require.NoError(t, err) + datasourceName := "test-cluster-vm" + dashboardReplacements := []struct { + commandPattern string + placeholder string + }{ + {`s/test-cluster/`, "test-cluster"}, + {`s/Test-Cluster/`, "Test-Cluster"}, + {`s/\${DS_.*-CLUSTER}/`, "${DS_TEST-CLUSTER}"}, + {`s/DS_.*-CLUSTER/`, "DS_TEST-CLUSTER"}, + {`s/\${DS_LIGHTNING}/`, "${DS_LIGHTNING}"}, + {`s/DS_LIGHTNING/`, "DS_LIGHTNING"}, + } // Create a mock for the execute function to handle the dashboard copy command origExecutor := &mockExecutor{ executeFunc: func(ctx context.Context, cmd string, sudo bool, timeouts ...time.Duration) ([]byte, []byte, error) { @@ -297,7 +309,7 @@ func TestVictoriaMetricsDefaultDatasource(t *testing.T) { return nil, nil, err } } else if strings.Contains(cmd, "sed") { - // Handle the sed command to replace datasource references + // Handle each sed command using the same replacement order as initDashboards. files, err := os.ReadDir(dashboardsDir) if err != nil { return nil, nil, err @@ -305,23 +317,21 @@ func TestVictoriaMetricsDefaultDatasource(t *testing.T) { for _, file := range files { if strings.HasSuffix(file.Name(), ".json") { - content, err := os.ReadFile(filepath.Join(dashboardsDir, file.Name())) + filePath := filepath.Join(dashboardsDir, file.Name()) + content, err := os.ReadFile(filePath) if err != nil { return nil, nil, err } - // Replace datasource references - simulating what sed would do - modifiedContent := strings.ReplaceAll(string(content), - `"DS_TEST-CLUSTER"`, - fmt.Sprintf(`"DS_%s-VM"`, strings.ToUpper("test-cluster"))) - modifiedContent = strings.ReplaceAll(modifiedContent, - `"text": "test-cluster"`, - fmt.Sprintf(`"text": "%s-vm"`, "test-cluster")) - modifiedContent = strings.ReplaceAll(modifiedContent, - `"value": "test-cluster"`, - fmt.Sprintf(`"value": "%s-vm"`, "test-cluster")) - - err = os.WriteFile(filepath.Join(dashboardsDir, file.Name()), []byte(modifiedContent), 0644) + modifiedContent := string(content) + for _, replacement := range dashboardReplacements { + if strings.Contains(cmd, replacement.commandPattern) { + modifiedContent = strings.ReplaceAll(modifiedContent, replacement.placeholder, datasourceName) + break + } + } + + err = os.WriteFile(filePath, []byte(modifiedContent), 0644) if err != nil { return nil, nil, err } @@ -335,7 +345,11 @@ func TestVictoriaMetricsDefaultDatasource(t *testing.T) { // Create a sample dashboard file with datasource references dashboardContent := `{ "annotations": { - "list": [] + "list": [ + { + "datasource": "${DS_TEST-CLUSTER}" + } + ] }, "editable": true, "fiscalYearStartMonth": 0, @@ -408,8 +422,15 @@ func TestVictoriaMetricsDefaultDatasource(t *testing.T) { content, err := os.ReadFile(dashboardFile) require.NoError(t, err) - // Verify VM datasource was used - assert.Contains(t, string(content), `"DS_TEST-CLUSTER-VM"`) + // Verify every dashboard reference uses the provisioned VM datasource name + // without rewriting it to test-cluster-vm-vm. + assert.Contains(t, string(content), `"datasource": "test-cluster-vm"`) + assert.Contains(t, string(content), `"name": "test-cluster-vm"`) assert.Contains(t, string(content), `"text": "test-cluster-vm"`) assert.Contains(t, string(content), `"value": "test-cluster-vm"`) + assert.NotContains(t, string(content), "test-cluster-vm-vm") + + dsContent, err := os.ReadFile(filepath.Join(deployDir, "provisioning", "datasources", "datasource.yml")) + require.NoError(t, err) + assert.Contains(t, string(dsContent), "name: test-cluster-vm") } diff --git a/pkg/cluster/spec/monitoring.go b/pkg/cluster/spec/monitoring.go index 743166e04a..28ba7bd585 100644 --- a/pkg/cluster/spec/monitoring.go +++ b/pkg/cluster/spec/monitoring.go @@ -255,6 +255,17 @@ func (i *MonitorInstance) handleRemoteWrite(spec *PrometheusSpec, monitoring *Pr } } +func addDashboardScrapeHosts(uniqueHosts set.StringSet, topoHasField func(string) (reflect.Value, bool)) { + servers, found := topoHasField("DashboardServers") + if !found { + return + } + for idx := 0; idx < servers.Len(); idx++ { + dashboard := servers.Index(idx).Interface().(*DashboardSpec) + uniqueHosts.Insert(dashboard.Host) + } +} + // InitConfig implement Instance interface func (i *MonitorInstance) InitConfig( ctx context.Context, @@ -465,6 +476,9 @@ func (i *MonitorInstance) InitConfig( cfig.AddDMWorker(host, uint64(port)) } } + // Keep this out of the uniqueHosts loops above: another if+for would + // push InitConfig over revive's cognitive-complexity limit of 110. + addDashboardScrapeHosts(uniqueHosts, topoHasField) if monitoredOptions != nil { for host := range uniqueHosts { diff --git a/pkg/cluster/spec/monitoring_test.go b/pkg/cluster/spec/monitoring_test.go index ce8b34e355..cb3bd236f1 100644 --- a/pkg/cluster/spec/monitoring_test.go +++ b/pkg/cluster/spec/monitoring_test.go @@ -23,10 +23,13 @@ import ( "testing" "github.com/pingcap/tiup/pkg/checkpoint" + "github.com/pingcap/tiup/pkg/cluster/ctxt" "github.com/pingcap/tiup/pkg/cluster/executor" + logprinter "github.com/pingcap/tiup/pkg/logger/printer" "github.com/pingcap/tiup/pkg/meta" "github.com/pingcap/tiup/pkg/utils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" ) @@ -440,3 +443,78 @@ func TestHandleRemoteWriteDisabled(t *testing.T) { assert.Len(t, spec.RemoteConfig.RemoteWrite, 1) assert.Equal(t, vmURL, spec.RemoteConfig.RemoteWrite[0]["url"]) } + +func TestStandaloneDashboardHostInNodeExporterTargets(t *testing.T) { + topo := &Specification{ + GlobalOptions: GlobalOptions{ + User: "tidb", + SystemdMode: UserMode, + }, + MonitoredOptions: MonitoredOptions{ + NodeExporterPort: 9100, + BlackboxExporterPort: 9115, + }, + PDServers: []*PDSpec{ + {Host: "10.0.1.11", ClientPort: 2379}, + }, + Monitors: []*PrometheusSpec{ + {Host: "10.0.1.21", Port: 9090}, + }, + DashboardServers: []*DashboardSpec{ + {Host: "10.0.1.50", Port: 12333}, + }, + } + + deployDir := t.TempDir() + cacheDir := t.TempDir() + paths := meta.DirPaths{ + Deploy: deployDir, + Cache: cacheDir, + Data: []string{filepath.Join(deployDir, "data")}, + Log: filepath.Join(deployDir, "log"), + } + require.NoError(t, os.MkdirAll(filepath.Join(deployDir, "bin", "prometheus"), 0755)) + + comp := MonitorComponent{Topology: topo} + prom := comp.Instances()[0].(*MonitorInstance) + err := prom.InitConfig( + ctxt.New(context.Background(), 0, logprinter.NewLogger("")), + &mockExecutor{}, + "verify-dashboard", + "v8.5.0", + "tidb", + paths, + ) + require.NoError(t, err) + + body, err := os.ReadFile(filepath.Join(deployDir, "conf", "prometheus.yml")) + require.NoError(t, err) + + var parsed struct { + ScrapeConfigs []struct { + JobName string `yaml:"job_name"` + StaticConfigs []struct { + Targets []string `yaml:"targets"` + } `yaml:"static_configs"` + } `yaml:"scrape_configs"` + } + require.NoError(t, yaml.Unmarshal(body, &parsed)) + + targetsOf := func(job string) []string { + var out []string + for _, sc := range parsed.ScrapeConfigs { + if sc.JobName != job { + continue + } + for _, cfg := range sc.StaticConfigs { + out = append(out, cfg.Targets...) + } + } + return out + } + + assert.Contains(t, targetsOf("overwritten-nodes"), "10.0.1.50:9100") + assert.Contains(t, targetsOf("overwritten-nodes"), "10.0.1.11:9100") + assert.Contains(t, targetsOf("overwritten-nodes"), "10.0.1.21:9100") + assert.Contains(t, targetsOf("monitor_port_probe"), "10.0.1.50:9115") +} diff --git a/pkg/cluster/task/tls.go b/pkg/cluster/task/tls.go index 892b64a6b7..2afdca9aef 100644 --- a/pkg/cluster/task/tls.go +++ b/pkg/cluster/task/tls.go @@ -71,6 +71,9 @@ func (c *TLSCert) Execute(ctx context.Context) error { // save cert to cache dir keyFileName := fmt.Sprintf("%s-%s-%d.pem", c.role, c.host, c.port) certFileName := fmt.Sprintf("%s-%s-%d.crt", c.role, c.host, c.port) + // Per-instance CA cache path. Parallel TLSCert tasks used to share + // cache/ca.crt and SCP a truncated file (#2727). + caFileName := fmt.Sprintf("%s-%s-%d-ca.crt", c.role, c.host, c.port) keyFile := filepath.Join( c.paths.Cache, keyFileName, @@ -79,7 +82,7 @@ func (c *TLSCert) Execute(ctx context.Context) error { c.paths.Cache, certFileName, ) - caFile := filepath.Join(c.paths.Cache, spec.TLSCACert) + caFile := filepath.Join(c.paths.Cache, caFileName) if err := utils.SaveFileWithBackup(keyFile, privKey.Pem(), ""); err != nil { return err } diff --git a/pkg/cluster/task/tls_test.go b/pkg/cluster/task/tls_test.go new file mode 100644 index 0000000000..81f6591fc7 --- /dev/null +++ b/pkg/cluster/task/tls_test.go @@ -0,0 +1,134 @@ +// Copyright 2026 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package task + +import ( + "context" + "encoding/pem" + "os" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/pingcap/tiup/pkg/cluster/ctxt" + "github.com/pingcap/tiup/pkg/cluster/spec" + "github.com/pingcap/tiup/pkg/crypto" + logprinter "github.com/pingcap/tiup/pkg/logger/printer" + "github.com/pingcap/tiup/pkg/meta" + "github.com/stretchr/testify/require" +) + +type overlappingCATransfer struct { + firstCA string + firstReady chan struct{} + readFirst chan struct{} + firstRead chan struct{} + mu sync.Mutex + transferred map[string][]byte +} + +func (*overlappingCATransfer) Execute(context.Context, string, bool, ...time.Duration) ([]byte, []byte, error) { + return nil, nil, nil +} + +func (e *overlappingCATransfer) Transfer(ctx context.Context, src, dst string, _ bool, _ int, _ bool) error { + if filepath.Base(dst) != spec.TLSCACert { + return nil + } + if dst == e.firstCA { + close(e.firstReady) + select { + case <-e.readFirst: + case <-ctx.Done(): + return ctx.Err() + } + data, err := os.ReadFile(src) + e.mu.Lock() + e.transferred[dst] = data + e.mu.Unlock() + close(e.firstRead) + return err + } + + data, err := os.ReadFile(src) + if err != nil { + return err + } + // Reproduce a competing writer's truncation window without relying on + // scheduler timing. It must not corrupt the first task's in-flight source. + if err := os.Truncate(src, 0); err != nil { + return err + } + close(e.readFirst) + select { + case <-e.firstRead: + case <-ctx.Done(): + return ctx.Err() + } + if err := os.WriteFile(src, data, 0600); err != nil { + return err + } + e.mu.Lock() + e.transferred[dst] = data + e.mu.Unlock() + return nil +} + +func TestTLSCertPreservesInFlightCA(t *testing.T) { + ca, err := crypto.NewCA("test") + require.NoError(t, err) + expected := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Cert.Raw}) + for _, tc := range []struct { + name string + host string + port int + }{ + {name: "different_hosts", host: "n2", port: 20160}, + {name: "same_host_different_ports", host: "n1", port: 20161}, + } { + t.Run(tc.name, func(t *testing.T) { + root := t.TempDir() + first := &TLSCert{comp: "tikv", role: "tikv", host: "n1", port: 20160, ca: ca, + paths: meta.DirPaths{Deploy: filepath.Join(root, "first"), Cache: filepath.Join(root, "cache")}} + second := &TLSCert{comp: "tikv", role: "tikv", host: tc.host, port: tc.port, ca: ca, + paths: meta.DirPaths{Deploy: filepath.Join(root, "second"), Cache: first.paths.Cache}} + firstCA := filepath.Join(first.paths.Deploy, spec.TLSCertKeyDir, spec.TLSCACert) + secondCA := filepath.Join(second.paths.Deploy, spec.TLSCertKeyDir, spec.TLSCACert) + e := &overlappingCATransfer{firstCA: firstCA, firstReady: make(chan struct{}), + readFirst: make(chan struct{}), firstRead: make(chan struct{}), transferred: make(map[string][]byte)} + base, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + ctx := ctxt.New(base, 2, logprinter.NewLogger("")) + ctxt.GetInner(ctx).SetExecutor(first.host, e) + ctxt.GetInner(ctx).SetExecutor(second.host, e) + done := make(chan error, 1) + go func() { done <- first.Execute(ctx) }() + select { + case <-e.firstReady: + case err := <-done: + require.NoError(t, err) + t.Fatal("first task did not reach CA transfer") + case <-ctx.Done(): + t.Fatal(ctx.Err()) + } + require.NoError(t, second.Execute(ctx)) + require.NoError(t, <-done) + e.mu.Lock() + defer e.mu.Unlock() + require.Equal(t, expected, e.transferred[firstCA]) + require.Equal(t, expected, e.transferred[secondCA]) + }) + } +} diff --git a/pkg/version/version.go b/pkg/version/version.go index 6e4ce8a251..3daf1c74b1 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -23,7 +23,7 @@ var ( // TiUPVerMinor is the minor version of TiUP TiUPVerMinor = 17 // TiUPVerPatch is the patch version of TiUP - TiUPVerPatch = 0 + TiUPVerPatch = 1 // TiUPVerName is an alternative name of the version TiUPVerName = "tiup" // GitHash is the current git commit hash diff --git a/tests/tiup-cluster/script/scale_core.sh b/tests/tiup-cluster/script/scale_core.sh index 570ea43102..5fa4e09b41 100755 --- a/tests/tiup-cluster/script/scale_core.sh +++ b/tests/tiup-cluster/script/scale_core.sh @@ -61,7 +61,7 @@ function scale_core() { topo=./topo/full_scale_in_tidb.yaml tiup-cluster $client --yes scale-out $name $topo # after scale-out, ensure the service is enabled - tiup-cluster $client exec $name -N n1 --command "systemctl status tidb-4000 | grep Loaded |grep 'enabled; vendor'" + tiup-cluster $client exec $name -N n1 --command "systemctl is-enabled --quiet tidb-4000" tiup-cluster $client exec $name -N n1 --command "grep -q n1:10080 /home/tidb/deploy/prometheus-9090/conf/prometheus.yml" assert_prometheus_external_labels $name n1 production us-east-1 diff --git a/tests/tiup-cluster/test_scale_core_tls.sh b/tests/tiup-cluster/test_scale_core_tls.sh index 0b9efabda2..2e7aeb92a5 100755 --- a/tests/tiup-cluster/test_scale_core_tls.sh +++ b/tests/tiup-cluster/test_scale_core_tls.sh @@ -4,5 +4,5 @@ set -eu source script/scale_core.sh -echo "test scaling of core components in cluster for version v5.3.0 w/ TLS, via easy ssh" -scale_core v4.0.12 true false +echo "test scaling of core components in cluster for version v6.0.0 w/ TLS, via easy ssh" +scale_core v6.0.0 true false diff --git a/tests/tiup-cluster/test_scale_tools.sh b/tests/tiup-cluster/test_scale_tools.sh index 7aca52a80d..5332b11f9e 100755 --- a/tests/tiup-cluster/test_scale_tools.sh +++ b/tests/tiup-cluster/test_scale_tools.sh @@ -4,5 +4,5 @@ set -eu source script/scale_tools.sh -echo "test scaling of tools components in cluster for version v4.0.12, via easy ssh" -scale_tools v4.0.12 false false +echo "test scaling of tools components in cluster for version v6.2.0, via easy ssh" +scale_tools v6.2.0 false false