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