blob: 10efa71cff1a4ec401c53719b1057aa47c2ebde8 [file] [log] [blame]
Alexandr Lovtsovd6254432020-10-15 17:53:34 +03001import org.jfrog.hudson.pipeline.common.types.ArtifactoryServer
2import java.util.concurrent.TimeoutException
3
4class Lock {
5 String name, id, path
6 Integer retryInterval, timeout, expiration
7 Boolean force
8 Map lockExtraMetadata
9 ArtifactoryServer artifactoryServer
10
11 private String lockFileContent
12 private String lockFileContentCache
13
14 final private String fileUri
15
16 final private def common = new com.mirantis.mk.Common()
17 final private def artifactoryTools = new com.mirantis.mk.Artifactory()
18
19 // Constructor
20 public Lock(Map args) {
21 // Mandatory
22 this.name = args.name
23 this.artifactoryServer = args.artifactoryServer
24
25 // Defaults
26 this.id = args.get('id', '')
27 this.path = args.get('path', 'binary-dev-local/locks')
28 this.retryInterval = args.get('retryInterval', 5*60)
29 this.timeout = args.get('timeout', 3*60*60)
30 this.expiration = args.get('expiration', 24*60*60)
31 this.force = args.get('force', false)
32 this.lockExtraMetadata = args.get('lockExtraMetadata', [:])
33
34 // Internal
35 this.fileUri = "/${path}/${name}.yaml".toLowerCase()
36 }
37
38 final private Map artObj
39 // getPasswordCredentials() is CPS-transformed function and cannot be used in constructor
40 final private Map getArtObj() {
41 def artifactoryCreds = common.getPasswordCredentials(artifactoryServer.getCredentialsId())
42 return [
43 'url': "${artifactoryServer.getUrl()}/artifactory",
44 'creds': [
45 'username': artifactoryCreds['username'],
46 'password': artifactoryCreds['password'],
47 ]
48 ]
49 }
50
51 // getter for lockFileContent
52 final private String getLockFileContent() {
53 if (this.lockFileContentCache == null) {
54 try {
55 this.lockFileContentCache = artifactoryTools.restCall(this.artObj, this.fileUri, 'GET', null, [:], '')
56 } catch (Exception e) {
57 this.lockFileContentCache = ''
58 }
59 }
60 return this.lockFileContentCache
61 }
62
63 public void lock() {
64 if (this.force) {
65 common.infoMsg("Ignore lock checking due 'force' flag presence")
66 } else {
67 waitLockReleased()
68 }
69 createLockFile()
70 }
71
72 public void unlock() {
73 if (!isLockFileExist()) {
74 common.infoMsg("Lock file '${this.artObj['url']}${this.fileUri}' does not exist. No need to remove it")
75 // No need to continue if file does not exist
76 return
77 }
78
79 Map lockMeta = common.readYaml2(text: this.lockFileContent ?: '{}')
80 if (this.force || (this.id && this.id == lockMeta.get('lockID', ''))) {
81 artifactoryTools.restCall(this.artObj, this.fileUri, 'DELETE', null, [:], '')
82 common.infoMsg("Lock file '${this.artObj['url']}${this.fileUri}' has been removed")
83 } else {
84 throw new RuntimeException("Given lock ID '${this.id}' is not equal to '${lockMeta.get('lockID')}' ID in lock file")
85 }
86 }
87
88 private void createLockFile() {
89 this.id = UUID.randomUUID().toString()
90
91 Calendar now = Calendar.getInstance()
92 Calendar expiredAt = now.clone()
93 expiredAt.add(Calendar.SECOND, this.expiration)
94
95 Map lockMeta = [
96 'lockID': this.id,
97 'createdAt': now.getTime().toString(),
98 'expiredAt': expiredAt.getTime().toString(),
99 ]
100 lockMeta.putAll(this.lockExtraMetadata)
101
102 def commonMCP = new com.mirantis.mcp.Common()
103 artifactoryTools.restCall(this.artObj, this.fileUri, 'PUT', commonMCP.dumpYAML(lockMeta), [:], '')
104 common.infoMsg("Lock file '${this.artObj['url']}${this.fileUri}' has been created")
105 }
106
107 private void waitLockReleased() {
108 Long startTime = System.currentTimeMillis()
109 while (isLocked()) {
110 if (System.currentTimeMillis() - startTime >= timeout*1000 ) {
111 throw new TimeoutException("Execution of waitLock timed out after ${this.timeout} seconds")
112 }
113 common.infoMsg("'${this.name}' is locked. Retry in ${this.retryInterval} seconds")
Alexandr Lovtsov7aca6e72020-10-20 13:00:11 +0300114 // Reset the cache so it will re-retrieve the file and its content
115 // otherwise it cannot determine that file has been removed on artifactory
116 // in the middle of waiting
117 this.lockFileContentCache = null
Alexandr Lovtsovd6254432020-10-15 17:53:34 +0300118 sleep(this.retryInterval*1000)
119 }
120 }
121
122 private Boolean isLocked() {
123 if (!isLockFileExist()) {
124 common.infoMsg("Lock file for '${this.name}' does not exist")
125 return false
126 } else if (isLockExpired()) {
127 common.infoMsg("Lock '${this.name}' has been expired")
128 return false
129 }
130 return true
131 }
132
133 private Boolean isLockFileExist() {
134 return !this.lockFileContent.isEmpty()
135 }
136
137 private Boolean isLockExpired() {
138 if (!isLockFileExist()) {
139 return true
140 }
141 Map lockMeta = common.readYaml2(text: this.lockFileContent ?: '{}')
142 Date expirationTime = new Date(lockMeta.get('expiredAt', '01/01/1970'))
143 Date currentTime = new Date()
144 return currentTime.after(expirationTime)
145 }
146}