Merge changes from topic 'sudars_19_music_291_1'
[music.git] / src / main / java / org / onap / music / lockingservice / zookeeper / ZkStatelessLockService.java
1 /*
2  * ============LICENSE_START========================================== org.onap.music
3  * ===================================================================
4  * Copyright (c) 2017 AT&T Intellectual Property 
5  * ===================================================================
6  * Modifications Copyright (c) 2018 IBM. 
7  * ===================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the License at
10  * 
11  * http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software distributed under the License
14  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
15  * or implied. See the License for the specific language governing permissions and limitations under
16  * the License.
17  * 
18  * ============LICENSE_END=============================================
19  * ====================================================================
20  */
21
22 package org.onap.music.lockingservice.zookeeper;
23
24
25 import java.util.List;
26 import java.util.SortedSet;
27 import java.util.TreeSet;
28 import org.apache.zookeeper.CreateMode;
29 import org.apache.zookeeper.KeeperException;
30 import org.apache.zookeeper.KeeperException.NoNodeException;
31 import org.apache.zookeeper.ZooDefs;
32 import org.apache.zookeeper.ZooKeeper;
33 import org.apache.zookeeper.data.ACL;
34 import org.apache.zookeeper.data.Stat;
35 import org.onap.music.datastore.PreparedQueryObject;
36 import org.onap.music.eelf.logging.EELFLoggerDelegate;
37 import org.onap.music.eelf.logging.format.AppMessages;
38 import org.onap.music.eelf.logging.format.ErrorSeverity;
39 import org.onap.music.eelf.logging.format.ErrorTypes;
40 import org.onap.music.main.MusicCore;
41 import org.onap.music.main.MusicUtil;
42 import org.onap.music.service.impl.MusicZKCore;
43
44 import com.datastax.driver.core.DataType;
45
46 /**
47  * A <a href="package.html">protocol to implement an exclusive write lock or to elect a leader</a>.
48  * <p/>
49  * You invoke {@link #lock()} to start the process of grabbing the lock; you may get the lock then
50  * or it may be some time later.
51  * <p/>
52  * You can register a listener so that you are invoked when you get the lock; otherwise you can ask
53  * if you have the lock by calling {@link #isOwner()}
54  *
55  */
56 public class ZkStatelessLockService extends ProtocolSupport {
57     public ZkStatelessLockService(ZooKeeper zk) {
58         zookeeper = zk;
59     }
60
61     private static EELFLoggerDelegate logger =
62                     EELFLoggerDelegate.getLogger(ZkStatelessLockService.class);
63
64     protected void createLock(final String path, final byte[] data) {
65         final List<ACL> acl = ZooDefs.Ids.OPEN_ACL_UNSAFE;
66         try {
67             retryOperation(new ZooKeeperOperation() {
68                 public boolean execute() throws KeeperException, InterruptedException {
69                     zookeeper.create(path, data, acl, CreateMode.PERSISTENT);
70                     return true;
71                 }
72             });
73         }catch (InterruptedException e) {
74             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.EXECUTIONINTERRUPTED, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
75         }catch (KeeperException e) {
76             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.KEEPERERROR, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
77         }
78     }
79
80     @Override
81     public void close() {
82         try {
83             zookeeper.close();
84         }catch (InterruptedException e) {
85             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.EXECUTIONINTERRUPTED, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
86         }
87     }
88
89     public void setNodeData(final String lockName, final byte[] data) {
90         try {
91             retryOperation(new ZooKeeperOperation() {
92                 public boolean execute() throws KeeperException, InterruptedException {
93                     zookeeper.getSessionId();
94                     zookeeper.setData("/" + lockName, data, -1);
95                     return true;
96                 }
97             });
98         }catch (InterruptedException e) {
99             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.EXECUTIONINTERRUPTED, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
100         }catch (KeeperException e) {
101             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.KEEPERERROR, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
102         }
103
104     }
105
106     public byte[] getNodeData(final String lockName) {
107         try {
108             if (zookeeper.exists("/" + lockName, null) != null)
109                 return zookeeper.getData("/" + lockName, false, null);
110             else
111                 return null;
112
113         }catch (InterruptedException e) {
114             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.EXECUTIONINTERRUPTED, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
115         }catch (KeeperException e) {
116             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.KEEPERERROR, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
117         }
118         return null;
119     }
120
121     public boolean checkIfLockExists(String lockName) {
122         boolean result = false;
123         try {
124             Stat stat = zookeeper.exists(lockName, false);
125             if (stat != null) {
126                 result = true;
127             }
128         }catch (InterruptedException e) {
129             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.EXECUTIONINTERRUPTED, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
130         }catch (KeeperException e) {
131             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.KEEPERERROR, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
132         }
133         return result;
134     }
135
136     public void createNode(String nodeName) {
137         ensurePathExists(nodeName);
138     }
139
140     public String createLockId(String dir) {
141         ensurePathExists(dir);
142         LockZooKeeperOperation zop = new LockZooKeeperOperation(dir);
143         try {
144             retryOperation(zop);
145         }catch (InterruptedException e) {
146             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.EXECUTIONINTERRUPTED, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
147         }catch (KeeperException e) {
148             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.KEEPERERROR, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
149         }
150         return zop.getId();
151     }
152
153     /**
154      * Attempts to acquire the exclusive write lock returning whether or not it was acquired. Note
155      * that the exclusive lock may be acquired some time later after this method has been invoked
156      * due to the current lock owner going away.
157      */
158     public synchronized boolean lock(String dir, String lockId)
159                     throws KeeperException, InterruptedException {
160         if (isClosed()) {
161             return false;
162         }
163         LockZooKeeperOperation zop = new LockZooKeeperOperation(dir, lockId);
164         return (Boolean) retryOperation(zop);
165     }
166
167     /**
168      * Removes the lock or associated znode if you no longer require the lock. this also removes
169      * your request in the queue for locking in case you do not already hold the lock.
170      * 
171      * @throws RuntimeException throws a runtime exception if it cannot connect to zookeeper.
172      * @throws NoNodeException 
173      */
174     public synchronized void unlock(String lockId) throws RuntimeException, KeeperException.NoNodeException {
175         final String id = lockId;
176         if (!isClosed() && id != null) {
177             try {
178                 ZooKeeperOperation zopdel = new ZooKeeperOperation() {
179                     public boolean execute() throws KeeperException, InterruptedException {
180                         zookeeper.delete(id, -1);
181                         return Boolean.TRUE;
182                     }
183                 };
184                 zopdel.execute();
185             } catch (InterruptedException e) {
186                 logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.EXECUTIONINTERRUPTED, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
187                 // set that we have been interrupted.
188                 Thread.currentThread().interrupt();
189             } catch (KeeperException.NoNodeException e) {
190                 // do nothing
191                 throw new KeeperException.NoNodeException("Lock doesn't exists. Release lock operation failed.");
192             } catch (KeeperException e) {
193                 logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.KEEPERERROR, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
194                 throw (RuntimeException) new RuntimeException(e.getMessage()).initCause(e);
195             }
196         }
197     }
198
199     public synchronized String currentLockHolder(String mainLock) {
200         final String id = mainLock;
201         if (!isClosed() && id != null) {
202             List<String> names;
203             try {
204                 names = zookeeper.getChildren(id, false);
205                 if (names.isEmpty())
206                     return "";
207                 SortedSet<ZNodeName> sortedNames = new TreeSet<>();
208                 for (String name : names) {
209                     sortedNames.add(new ZNodeName(id + "/" + name));
210                 }
211                 return sortedNames.first().getName();
212             } catch (InterruptedException e) {
213                 logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.EXECUTIONINTERRUPTED, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
214                 // set that we have been interrupted.
215                 Thread.currentThread().interrupt();
216             } catch (KeeperException.NoNodeException e) {
217                 // do nothing
218             } catch (KeeperException e) {
219                 logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.KEEPERERROR, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
220                 throw (RuntimeException) new RuntimeException(e.getMessage()).initCause(e);
221             }
222         }
223         return "No lock holder!";
224     }
225
226     public synchronized void deleteLock(String mainLock) {
227         final String id = mainLock;
228         if (!isClosed() && id != null) {
229             try {
230                 ZooKeeperOperation zopdel = new ZooKeeperOperation() {
231                     public boolean execute() throws KeeperException, InterruptedException {
232                         List<String> names = zookeeper.getChildren(id, false);
233                         for (String name : names) {
234                             zookeeper.delete(id + "/" + name, -1);
235                         }
236                         zookeeper.delete(id, -1);
237                         return Boolean.TRUE;
238                     }
239                 };
240                 zopdel.execute();
241             } catch (InterruptedException e) {
242                 logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.EXECUTIONINTERRUPTED, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
243                 // set that we have been interrupted.
244                 Thread.currentThread().interrupt();
245             } catch (KeeperException.NoNodeException e) {
246                 logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.KEEPERERROR, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
247                 // do nothing
248             } catch (KeeperException e) {
249                 logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.KEEPERERROR, ErrorSeverity.ERROR, ErrorTypes.LOCKINGERROR);
250                 throw (RuntimeException) new RuntimeException(e.getMessage()).initCause(e);
251             }
252         }
253
254     }
255
256     /**
257      * a zoookeeper operation that is mainly responsible for all the magic required for locking.
258      */
259     private class LockZooKeeperOperation implements ZooKeeperOperation {
260
261         /**
262          * find if we have been created earlier if not create our node
263          * 
264          * @param prefix the prefix node
265          * @param zookeeper the zookeeper client
266          * @param dir the dir parent
267          * @throws KeeperException
268          * @throws InterruptedException
269          */
270         private String dir;
271         private String id = null;
272
273         public String getId() {
274             return id;
275         }
276
277         public LockZooKeeperOperation(String dir) {
278             this.dir = dir;
279         }
280
281         public LockZooKeeperOperation(String dir, String id) {
282             this.dir = dir;
283             this.id = id;
284         }
285
286         /**
287          * the command that is run and retried for actually obtaining the lock
288          * 
289          * @return if the command was successful or not
290          */
291         public boolean execute() throws KeeperException, InterruptedException {
292             do {
293                 if (id == null) {
294                     String prefix = "x-";
295                     byte[] data = {0x12, 0x34};
296                     id = zookeeper.create(dir + "/" + prefix, data, getAcl(),
297                                     CreateMode.PERSISTENT_SEQUENTIAL);
298
299                     if (logger.isDebugEnabled()) {
300                         logger.debug(EELFLoggerDelegate.debugLogger, "Created id: " + id);
301                     }
302                     if (id != null) {
303                         Stat stat = null;
304                         try {
305                             stat = zookeeper.exists(id, false);
306                         } catch (KeeperException | InterruptedException e1) {
307                             e1.printStackTrace();
308                         }
309                         Long ctime = stat.getCtime();
310                         MusicUtil.zkNodeMap.put(id, ctime);
311                         PreparedQueryObject pQuery = new PreparedQueryObject();
312                         pQuery.appendQueryString(
313                                         "INSERT INTO admin.locks(lock_id, ctime) VALUES (?,?)");
314                         try {
315                             pQuery.addValue(MusicUtil.convertToActualDataType(DataType.text(), id));
316                             pQuery.addValue(MusicUtil.convertToActualDataType(DataType.text(), ctime));
317                             MusicCore.eventualPut(pQuery);
318                         } catch (Exception e) {
319                                e.printStackTrace();
320                         }
321                         break;
322                    }
323                 }
324                 if (id != null) {
325                     List<String> names = zookeeper.getChildren(dir, false);
326                     if (names.isEmpty()) {
327                         logger.info(EELFLoggerDelegate.applicationLogger, "No children in: " + dir
328                                         + " when we've just " + "created one! Lets recreate it...");
329                         // lets force the recreation of the id
330                         id = null;
331                         return Boolean.FALSE;
332
333                     } else {
334                         // lets sort them explicitly (though they do seem to come back in order
335                         // ususally :)
336                         ZNodeName idName = new ZNodeName(id);
337                         SortedSet<ZNodeName> sortedNames = new TreeSet<>();
338                         for (String name : names) {
339                             sortedNames.add(new ZNodeName(dir + "/" + name));
340                         }
341                         if (!sortedNames.contains(idName))
342                             return Boolean.FALSE;
343
344                         SortedSet<ZNodeName> lessThanMe = sortedNames.headSet(idName);
345                         if (!lessThanMe.isEmpty()) {
346                             ZNodeName lastChildName = lessThanMe.last();
347                             String lastChildId = lastChildName.getName();
348                             if (logger.isDebugEnabled()) {
349                                 logger.debug(EELFLoggerDelegate.debugLogger, "watching less than me node: " + lastChildId);
350                             }
351                             Stat stat = zookeeper.exists(lastChildId, false);
352                             if (stat != null) {
353                                 return Boolean.FALSE;
354                             } else {
355                                 logger.info(EELFLoggerDelegate.applicationLogger,
356                                                 "Could not find the" + " stats for less than me: "
357                                                                 + lastChildName.getName());
358                             }
359                         } else
360                             return Boolean.TRUE;
361                     }
362                 }
363             } while (id == null);
364             return Boolean.FALSE;
365         }
366     }
367
368 }
369