This is an automated email from the ASF dual-hosted git repository.

ocket8888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 66e98cb  Add t3c retrying app lock (#6357)
66e98cb is described below

commit 66e98cb8cc1e8b329ff860e67905040568fccdf0
Author: Robert O Butts <[email protected]>
AuthorDate: Thu Nov 18 16:33:30 2021 -0700

    Add t3c retrying app lock (#6357)
    
    * Add t3c retrying app lock
    
    * Remove test function newlines
    
    Per PR Review.
    
    * Change variable to const
    
    Per PR Review.
    
    * Change format strings to types
    
    Per PR Review.
---
 CHANGELOG.md                                       |   1 +
 cache-config/t3c-apply/t3c-apply.go                |  15 ++-
 cache-config/t3c-apply/util/util.go                |   1 -
 .../testing/ort-tests/t3c-lockfile_test.go         | 113 +++++++++++++++++++++
 4 files changed, 127 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 27ff4ad..c087f6e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 - Added definition for `heartbeat.polling.interval` for CDN Traffic Monitor 
config in API documentation.
 - New `pkg` script options, `-h`, `-s`, `-S`, and `-L`.
 - Added `Invalidation Type` (REFRESH or REFETCH) for invalidating content to 
Traffic Portal.
+- cache config t3c-apply retrying when another t3c-apply is running.
 - IMS warnings to Content Invalidation requests in Traffic Portal and 
documentation.
 - [#6032](https://github.com/apache/trafficcontrol/issues/6032) Add t3c 
setting mode 0600 for secure files
 
diff --git a/cache-config/t3c-apply/t3c-apply.go 
b/cache-config/t3c-apply/t3c-apply.go
index 992cf80..b685145 100644
--- a/cache-config/t3c-apply/t3c-apply.go
+++ b/cache-config/t3c-apply/t3c-apply.go
@@ -59,6 +59,10 @@ func runSysctl(cfg config.Cfg) {
        }
 }
 
+const LockFilePath = "/var/run/t3c.lock"
+const LockFileRetryInterval = time.Second
+const LockFileRetryTimeout = time.Minute
+
 func main() {
        var syncdsUpdate torequest.UpdateStatus
        var lock util.FileLock
@@ -69,9 +73,16 @@ func main() {
        } else if cfg == (config.Cfg{}) { // user used the --help option
                os.Exit(Success)
        }
-       if !lock.GetLock("/var/run/t3c.lock") {
-               os.Exit(AlreadyRunning)
+
+       log.Infoln("Trying to acquire app lock")
+       for lockStart := time.Now(); !lock.GetLock(LockFilePath); {
+               if time.Since(lockStart) > LockFileRetryTimeout {
+                       log.Errorf("Failed to get app lock after %v seconds, 
another instance is running, exiting without running\n", 
int(LockFileRetryTimeout/time.Second))
+                       os.Exit(AlreadyRunning)
+               }
+               time.Sleep(LockFileRetryInterval)
        }
+       log.Infoln("Acquired app lock")
 
        if cfg.UseGit == config.UseGitYes {
                err := util.EnsureConfigDirIsGitRepo(cfg)
diff --git a/cache-config/t3c-apply/util/util.go 
b/cache-config/t3c-apply/util/util.go
index 7f8c36d..4a10aee 100644
--- a/cache-config/t3c-apply/util/util.go
+++ b/cache-config/t3c-apply/util/util.go
@@ -81,7 +81,6 @@ func (f *FileLock) GetLock(lockFile string) bool {
                return false
        }
        if !f.is_locked { // another process is running.
-               log.Errorf("Another t3c process is already running, try again 
later\n")
                return false
        }
 
diff --git a/cache-config/testing/ort-tests/t3c-lockfile_test.go 
b/cache-config/testing/ort-tests/t3c-lockfile_test.go
new file mode 100644
index 0000000..2dce73d
--- /dev/null
+++ b/cache-config/testing/ort-tests/t3c-lockfile_test.go
@@ -0,0 +1,113 @@
+package orttest
+
+/*
+   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,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+       "strings"
+       "sync"
+       "testing"
+       "time"
+
+       "github.com/apache/trafficcontrol/cache-config/testing/ort-tests/tcdata"
+)
+
+func TestLockfile(t *testing.T) {
+       testName := "TestLockfile"
+       t.Logf("------------- Starting " + testName + " ---------------")
+       tcd.WithObjs(t, []tcdata.TCObj{
+               tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters,
+               tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses,
+               tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations,
+               tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies,
+               tcdata.DeliveryServices}, func() {
+
+               const hostName = `atlanta-edge-03`
+               const fileName = `records.config`
+
+               firstOut := ""
+               firstCode := 0
+               wg := sync.WaitGroup{}
+               wg.Add(1)
+               go func() {
+                       defer wg.Done()
+                       t.Logf("TestLockFile first t3c starting %s", time.Now())
+                       firstOut, firstCode = t3cUpdateReload(hostName, 
"badass")
+                       t.Logf("TestLockFile first t3c finished %s", time.Now())
+               }()
+
+               time.Sleep(time.Millisecond * 100) // sleep long enough to 
ensure the concurrent t3c starts
+               t.Logf("TestLockFile second t3c starting %s", time.Now())
+               out, code := t3cUpdateReload(hostName, "badass")
+               t.Logf("TestLockFile second t3c finished %s", time.Now())
+               if code != 0 {
+                       t.Fatalf("second t3c apply badass failed: output 
'''%s''' code %d", out, code)
+               }
+
+               wg.Wait()
+               if firstCode != 0 {
+                       t.Fatalf("first t3c apply badass failed: output 
'''%s''' code %d", firstOut, firstCode)
+               }
+
+               outStr := string(out)
+               outLines := strings.Split(outStr, "\n")
+               acquireStartLine := ""
+               acquireEndLine := ""
+               for _, line := range outLines {
+                       if strings.Contains(line, "Trying to acquire app lock") 
{
+                               acquireStartLine = line
+                       } else if strings.Contains(line, "Acquired app lock") {
+                               acquireEndLine = line
+                       }
+                       if acquireStartLine != "" && acquireEndLine != "" {
+                               break
+                       }
+               }
+
+               if acquireStartLine == "" || acquireEndLine == "" {
+                       t.Fatalf("t3c apply output expected to contain 'Trying 
to acquire app lock' and 'Acquired app lock', actual: '''%s'''", out)
+               }
+
+               acquireStart := parseLogLineTime(acquireStartLine)
+               if acquireStart == nil {
+                       t.Fatalf("t3c apply acquire line failed to parse time, 
line '" + acquireStartLine + "'")
+               }
+               acquireEnd := parseLogLineTime(acquireEndLine)
+               if acquireEnd == nil {
+                       t.Fatalf("t3c apply acquire line failed to parse time, 
line '" + acquireEndLine + "'")
+               }
+
+               minDiff := time.Second * 1 // checking the file lock should 
never take 1s, so that's enough to verify it was hit
+               if diff := acquireEnd.Sub(*acquireStart); diff < minDiff {
+                       t.Fatalf("t3c apply expected time to acquire while 
another t3c is running to be at least %s, actual %s start line '%s' end line 
'%s'", minDiff, diff, acquireStartLine, acquireEndLine)
+               }
+
+               t.Logf(testName + " succeeded")
+       })
+       t.Logf("------------- End of " + testName + " ---------------")
+}
+
+func parseLogLineTime(line string) *time.Time {
+       fields := strings.Fields(line)
+       if len(fields) < 3 {
+               return nil
+       }
+       timeStr := fields[2]
+       timeStr = strings.TrimSuffix(timeStr, ":")
+       tm, err := time.Parse(time.RFC3339Nano, timeStr)
+       if err != nil {
+               return nil
+       }
+       return &tm
+}

Reply via email to